Bokeh Heatmap Legend - python

I am trying to create a legend for a heatmap in bokeh. I decided that creating a separate plot that will stand to the right of my heatmap was the best way to go about it because there are a lot of custom calculations. I have the below code but for some reason only the first 2 colors are showing. I can't seem to find what is wrong. Also, how can I display custom labels instead of y-axis values in this chart? For example, how would I display 'Label_1' in place of '1' on the y-axis?
Thanks
from bokeh.plotting import ColumnDataSource, figure, output_file, show
import numpy as np
from collections import OrderedDict
color = []
val = []
color.append('rgb(255,255,255)')
val.append('1')
color.append('rgb(204,229,255)')
val.append('2')
color.append('rgb(153,204,255)')
val.append('3')
color.append('rgb(102,178,255)')
val.append('4')
color.append('rgb(51,153,255)')
val.append('5')
color.append('rgb(0,128,255)')
val.append('6')
color.append('rgb(0,102,204)')
val.append('7')
color.append('rgb(0,25,51)')
val.append('8')
source = ColumnDataSource(
data=OrderedDict(color=color,val=val))
p = figure(title=None,x_range=[0,1], y_range=val)
p.rect([0,1], 'val', 1, 1, source=source, color='color')
p.plot_width = 100
p.plot_height = 500
p.grid.grid_line_color = None
p.axis.axis_line_color = None
p.axis.major_tick_line_color = None
p.axis.major_label_text_font_size = "9pt"
p.axis.major_label_standoff = 0
p.xaxis.major_label_orientation = np.pi/2
show(p)
output_file('heatmap_legend.html')

This was an issue with the 'height' of the bars. You need to assign a height of 1 for each element in the array that is passed to Bokeh.

Related

how to add secondary x-axis with my String labels

I have plot
with secondary axis added like this:
plot.extra_x_ranges['sec_x_axis'] = Range1d(0, 100)
ax2 = LinearAxis(x_range_name="sec_x_axis", axis_label="secondary x-axis")
plot.add_layout(ax2, 'above')
x_axis is x_axis_type='datetime', so bokeh show ms on second x-axis too. This is not good.
Is there a way I can put my labels on this axis? I have a list of str labels like:
my_labels = ['21.5; 315.1', '21.6; 315.0', '21.7; 315.0', '21.7; 314.9',.....]
I found FuncTickFormatter but it takes JS code inside, so I can't handle it.
Maybe there is another way to do this?
To override the values of the labels use major_label_overrides on the appropriate axis. You can pass a dictionary like {1:'A', ...}, where 1 is the place to overwrite and A is the new label.
To avoid "wrong" labels while zooming, you can set the ticker direcetlly as list unsing ticker.
In your case the axis is p.above[0].
Comment
If you add a LinearAxis to a figure with an already existing DatetimeAxis, the new axis shoudn't be effected and therefor shouldn't be formatted as datetime. I used the latest version 2.4.3 and it works as expected. Use the minimal example to try it on your own.
Minimal Example
This code is based on the twin_axis.py example published by the authors of bokeh.
from numpy import arange, linspace, pi, sin
from bokeh.models import LinearAxis, Range1d
from bokeh.plotting import figure, show, output_notebook
output_notebook()
x = arange(-2*pi, 2*pi, 0.2)
x2 = arange(-pi, pi, 0.1)
y = sin(x)
y2 = sin(x2)
p = figure(
width=400,
height=400,
x_range=(-6.5, 6.5),
y_range=(-1.1, 1.1),
min_border=80,
x_axis_type="datetime"
)
p.circle(x, y, color="crimson", size=8)
p.yaxis.axis_label = "red circles"
p.yaxis.axis_label_text_color ="crimson"
p.extra_x_ranges['foo'] = Range1d(-pi, pi)
p.circle(x2, y2, color="navy", size=8, x_range_name="foo")
ax2 = LinearAxis(x_range_name="foo", axis_label="blue circles")
ax2.axis_label_text_color ="navy"
p.add_layout(ax2, 'above')
# set ticker to avoid wrong formatted labels while zooming
p.above[0].ticker = list(range(-3,4))
# overwrite labels
p.above[0].major_label_overrides = {key: item for key, item in zip(range(-3,4), list('ABCDEFG'))}
show(p)
default
overwritten labels

pyplot.contourf and branca colormap does not show the same colors in folium

I am trying to display a contourf plot as you can see below in the code snippet on a Folium map.
I can see the filled contour plot just fine. Also, I added a color bar with exact same colors using branca at this line:
bmap = branca.colormap.LinearColormap(colorl, vmin=levs[0],
vmax=levs[-1]).to_step(len(levs),index=levs)
geojsonf = geojsoncontour.contourf_to_geojson(
contourf=pcontf,
min_angle_deg=3.0,
ndigits=5,
stroke_width=1,
fill_opacity=0.9)
As you can see in the output image, colors don't match.
I suspect opacity I use for the contour plot might play a role here but changing opacity does not make it better.
I also tried making Circle markers (not shown here) with the same colors but still no luck. I cannot get pyplot colors to match.
Any suggestion is greatly appreciated. Also is there a better way to accomplish the same task? I basically have a 2D NumPy array with values ranging from -50 to 50on a reprojected lat-lon grid. I need to be able to show the shaded contours and associated values in the bar.
fig = plt.figure(figsize=[10, 15], dpi=None)
ax = fig.subplots()
jet =plt.get_cmap('jet')
clevs= np.array(levs)
cnorm = plt.Normalize(vmin=levs[0],vmax=levs[-1])
clevels = [levs[0]] + list(0.5*(clevs[1:]+clevs[:-1])) + [levs[-1]]
colors=jet(cnorm(clevels))
colorsm = color.ListedColormap(colors)
pcontf = ax.contourf(lons,lats,data,levels=levs,cmap=colorsm)
mapa = folium.Map([np.mean(lats), np.mean(lons)], zoom_start=10,tiles='Stamen Terrain')
colorl = []
for i,val in enumerate(colors):
carr= colors[i-1]
ccol = (carr[1],carr[2],carr[3])
colorl.insert(i,ccol)
bmap = branca.colormap.LinearColormap(colorl, vmin=levs[0],
vmax=levs[-1]).to_step(len(levs),index=levs)
geojsonf = geojsoncontour.contourf_to_geojson(
contourf=pcontf,
min_angle_deg=3.0,
ndigits=5,
stroke_width=1,
fill_opacity=0.9)
folium.GeoJson(
geojsonf,
style_function=lambda x: {
'color': x['properties']['stroke'],
'weight': x['properties']['stroke-width'],
'fillColor': x['properties']['fill'],
'opacity': 0.9,
}).add_to(mapa)
bmap.add_to(mapa)
I believe you have to recreate the Matplotlib colormap in Folium first.
This is how I did it (in the example my values range from 0 to 310, m is the Folium map):
Creating a colormap with Matplotlib:
import matplotlib as mpl
import branca.colormap as cm
import numpy as np
cmap = mpl.cm.get_cmap(name='rainbow',lut=310)
Creating a list with appropriate step size:
index_list = range(0,310,10).tolist()
cmap_list = cmap(index_list).tolist()
Creating a Folium colormap identical to the Matplotlib colormap:
cmap_foliump = cm.LinearColormap(
cmap_list, vmin=0, vmax=310, index=index_list, caption='my colormap')
Adding to Folium map:
m.add_child(cmap_folium)
Make sure index_list is comprised of true integers. If you create it with numpy.arange() just add .astype(int) prior to tolist()

python bokeh: update scatter plot colors on callback

I only started to use Bokeh recently. I have a scatter plot in which I would like to color each marker according to a certain third property (say a quantity, while the x-axis is a date and the y-axis is a given value at that point in time).
Assuming my data is in a data frame, I managed to do this using a linear color map as follows:
min_q = df.quantity.min()
max_q = df.quantity.max()
mapper = linear_cmap(field_name='quantity', palette=palettes.Spectral6, low=min_q, high=max_q)
source = ColumnDataSource(data=get_data(df))
p = figure(x_axis_type="datetime")
p.scatter(x="date_column", y="value", marker="triangle", fill_color=mapper, line_color=None, source=source)
color_bar = ColorBar(color_mapper=mapper['transform'], width=8, location=(0,0))
p.add_layout(color_bar, 'right')
This seems to work as expected. Below is the plot I get upon starting the bokeh server.
Then I have a callback function update() triggered upon changing value in some widget (a select or a time picker).
def update():
# get new df (according to new date/select)
df = get_df()
# update min/max for colormap
min_q = df.quantity.min()
max_q = df.quantity.max()
# I think I should not create a new mapper but doing so I get closer
mapper = linear_cmap(field_name='quantity', palette=palettes.Spectral6 ,low=min_q, high=max_q)
color_bar.color_mapper=mapper['transform']
source.data = get_data(df)
# etc
This is the closest I could get. The color map is updated with new values, but it seems that the colors of the marker still follow the original pattern. See picture below (given that quantity I would expect green, but it is blue as it still seen as < 4000 as in the map of the first plot before the callback).
Should I just add a "color" column to the data frame? I feel there is an easier/more convenient way to do that.
EDIT: Here is a minimal working example using the answer by bigreddot:
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.plotting import figure
from bokeh.models import Button, ColumnDataSource, ColorBar, HoverTool
from bokeh.palettes import Spectral6
from bokeh.transform import linear_cmap
import numpy as np
x = [1,2,3,4,5,7,8,9,10]
y = [1,2,3,4,5,7,8,9,10]
z = [1,2,3,4,5,7,8,9,10]
source = ColumnDataSource(dict(x=x, y=y, z=z))
#Use the field name of the column source
mapper = linear_cmap(field_name='z', palette=Spectral6 ,low=min(y) ,high=max(y))
p = figure(plot_width=300, plot_height=300, title="Linear Color Map Based on Y")
p.circle(x='x', y='y', line_color=mapper,color=mapper, fill_alpha=1, size=12, source=source)
color_bar = ColorBar(color_mapper=mapper['transform'], width=8, location=(0,0))
p.add_tools(HoverTool(tooltips="#z", show_arrow=False, point_policy='follow_mouse'))
p.add_layout(color_bar, 'right')
b = Button()
def update():
new_z = np.exp2(z)
mapper = linear_cmap(field_name='z', palette=Spectral6 ,low=min(new_z), high=max(new_z))
color_bar.color_mapper=mapper['transform']
source.data = dict(x=x, y=y, z=new_z)
b.on_click(update)
curdoc().add_root(column(b, p))
Upon update, the circles will be colored according to the original scale: everything bigger than 10 will be red. Instead, I would expect everything blue until the last 3 circle on tops that should be colored green yellow and red respectively.
It's possible that is a bug, feel free to open a GitHub issue.
That said, the above code does not represent best practices for Bokeh usage, which is: always make the smallest update possible. In this case, this means setting new property values on the existing color transform, rather than replacing the existing color transform.
Here is a complete working example (made with Bokeh 1.0.2) that demonstrates the glyph's colormapped colors updating in response to the data column changing:
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.plotting import figure
from bokeh.models import Button, ColumnDataSource, ColorBar
from bokeh.palettes import Spectral6
from bokeh.transform import linear_cmap
x = [1,2,3,4,5,7,8,9,10]
y = [1,2,3,4,5,7,8,9,10]
z = [1,2,3,4,5,7,8,9,10]
#Use the field name of the column source
mapper = linear_cmap(field_name='z', palette=Spectral6 ,low=min(y) ,high=max(y))
source = ColumnDataSource(dict(x=x, y=y, z=z))
p = figure(plot_width=300, plot_height=300, title="Linear Color Map Based on Y")
p.circle(x='x', y='y', line_color=mapper,color=mapper, fill_alpha=1, size=12, source=source)
color_bar = ColorBar(color_mapper=mapper['transform'], width=8, location=(0,0))
p.add_layout(color_bar, 'right')
b = Button()
def update():
new_z = np.exp2(z)
# update the existing transform
mapper['transform'].low=min(new_z)
mapper['transform'].high=max(new_z)
source.data = dict(x=x, y=y, z=new_z)
b.on_click(update)
curdoc().add_root(column(b, p))
Here is the original plot:
And here is the update plot after clicking the button

How to draw a circle plot the LinearColorMapper using python Bokeh

With the following code,
from bokeh.plotting import figure, show, output_file
from bokeh.sampledata.iris import flowers
colormap = {'setosa': 'red', 'versicolor': 'green', 'virginica': 'blue'}
colors = [colormap[x] for x in flowers['species']]
p = figure(title = "Iris Morphology")
p.xaxis.axis_label = 'Petal Length'
p.yaxis.axis_label = 'Petal Width'
p.circle(flowers["petal_length"], flowers["petal_width"],
color=colors, fill_alpha=0.2, size=10)
output_file("iris.html", title="iris.py example")
show(p)
I can make a circle plot where I color the species:
But what I want to do is to color all the point based on range of
value in petal_length.
I tried this code but fail:
from bokeh.models import LinearColorMapper
exp_cmap = LinearColorMapper(palette='Viridis256', low = min(flowers["petal_length"]), high = max(flowers["petal_length"]))
p.circle(flowers["petal_length"], flowers["petal_width"],
fill_color = {'field' : flowers["petal_lengh"], 'transform' : exp_cmap})
output_file("iris.html", title="iris.py example")
show(p)
And also in the final desired plot, how can I put the color bar that
show the range of values and the assigned value. Something like this:
I'm using Python 2.7.13.
To answer your first part, there was a small typo (petal_lengh instead of petal_length) but more importantly, using the bokeh.ColumnDataSource will solve your problem (I tried to do it without CDS and only got column errors):
from bokeh.plotting import figure, show, output_file
from bokeh.sampledata.iris import flowers
from bokeh.models import LinearColorMapper
from bokeh.models import ColumnDataSource
p = figure(title = "Iris Morphology")
p.xaxis.axis_label = "Petal Length"
p.yaxis.axis_label = "Petal Width"
source = ColumnDataSource(flowers)
exp_cmap = LinearColorMapper(palette="Viridis256",
low = min(flowers["petal_length"]),
high = max(flowers["petal_length"]))
p.circle("petal_length", "petal_width", source=source, line_color=None,
fill_color={"field":"petal_length", "transform":exp_cmap})
# ANSWER SECOND PART - COLORBAR
# To display a color bar you'll need to import
# the `bokeh.models.ColorBar` class and pass it your mapper.
from bokeh.models import ColorBar
bar = ColorBar(color_mapper=exp_cmap, location=(0,0))
p.add_layout(bar, "left")
show(p)
See also: https://github.com/bokeh/bokeh/blob/master/examples/plotting/file/color_data_map.py
The colormapper transform refers to a column name and does not accept actual literal lists of data. So all the data needs to be in a Bokeh ColumDataSource and the plotting funcs all need to refer to the column names. Fortunately this is straightforward:
p.circle("petal_length", "petal_width", source=flowers, size=20,
fill_color = {'field': 'petal_length', 'transform': exp_cmap})
Directions for legends outside the plot area are documented here:
https://docs.bokeh.org/en/latest/docs/user_guide/styling.html#outside-the-plot-area

MatPlotlib Seaborn Multiple Plots formatting

I am translating a set of R visualizations to Python. I have the following target R multiple plot histograms:
Using Matplotlib and Seaborn combination and with the help of a kind StackOverflow member (see the link: Python Seaborn Distplot Y value corresponding to a given X value), I was able to create the following Python plot:
I am satisfied with its appearance, except, I don't know how to put the Header information in the plots. Here is my Python code that creates the Python Charts
""" Program to draw the sampling histogram distributions """
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
import seaborn as sns
def main():
""" Main routine for the sampling histogram program """
sns.set_style('whitegrid')
markers_list = ["s", "o", "*", "^", "+"]
# create the data dataframe as df_orig
df_orig = pd.read_csv('lab_samples.csv')
df_orig = df_orig.loc[df_orig.hra != -9999]
hra_list_unique = df_orig.hra.unique().tolist()
# create and subset df_hra_colors to match the actual hra colors in df_orig
df_hra_colors = pd.read_csv('hra_lookup.csv')
df_hra_colors['hex'] = np.vectorize(rgb_to_hex)(df_hra_colors['red'], df_hra_colors['green'], df_hra_colors['blue'])
df_hra_colors.drop(labels=['red', 'green', 'blue'], axis=1, inplace=True)
df_hra_colors = df_hra_colors.loc[df_hra_colors['hra'].isin(hra_list_unique)]
# hard coding the current_component to pc1 here, we will extend it by looping
# through the list of components
current_component = 'pc1'
num_tests = 5
df_columns = df_orig.columns.tolist()
start_index = 5
for test in range(num_tests):
current_tests_list = df_columns[start_index:(start_index + num_tests)]
# now create the sns distplots for each HRA color and overlay the tests
i = 1
for _, row in df_hra_colors.iterrows():
plt.subplot(3, 3, i)
select_columns = ['hra', current_component] + current_tests_list
df_current_color = df_orig.loc[df_orig['hra'] == row['hra'], select_columns]
y_data = df_current_color.loc[df_current_color[current_component] != -9999, current_component]
axs = sns.distplot(y_data, color=row['hex'],
hist_kws={"ec":"k"},
kde_kws={"color": "k", "lw": 0.5})
data_x, data_y = axs.lines[0].get_data()
axs.text(0.0, 1.0, row['hra'], horizontalalignment="left", fontsize='x-small',
verticalalignment="top", transform=axs.transAxes)
for current_test_index, current_test in enumerate(current_tests_list):
# this_x defines the series of current_component(pc1,pc2,rhob) for this test
# indicated by 1, corresponding R program calls this test_vector
x_series = df_current_color.loc[df_current_color[current_test] == 1, current_component].tolist()
for this_x in x_series:
this_y = np.interp(this_x, data_x, data_y)
axs.plot([this_x], [this_y - current_test_index * 0.05],
markers_list[current_test_index], markersize = 3, color='black')
axs.xaxis.label.set_visible(False)
axs.xaxis.set_tick_params(labelsize=4)
axs.yaxis.set_tick_params(labelsize=4)
i = i + 1
start_index = start_index + num_tests
# plt.show()
pp = PdfPages('plots.pdf')
pp.savefig()
pp.close()
def rgb_to_hex(red, green, blue):
"""Return color as #rrggbb for the given color values."""
return '#%02x%02x%02x' % (red, green, blue)
if __name__ == "__main__":
main()
The Pandas code works fine and it is doing what it is supposed to. It is my lack of knowledge and experience of using 'PdfPages' in Matplotlib that is the bottleneck. How can I show the header information in Python/Matplotlib/Seaborn that I can show in the corresponding R visalization. By the Header information, I mean What The R visualization has at the top before the histograms, i.e., 'pc1', MRP, XRD,....
I can get their values easily from my program, e.g., current_component is 'pc1', etc. But I don't know how to format the plots with the Header. Can someone provide some guidance?
You may be looking for a figure title or super title, fig.suptitle:
fig.suptitle('this is the figure title', fontsize=12)
In your case you can easily get the figure with plt.gcf(), so try
plt.gcf().suptitle("pc1")
The rest of the information in the header would be called a legend.
For the following let's suppose all subplots have the same markers. It would then suffice to create a legend for one of the subplots.
To create legend labels, you can put the labelargument to the plot, i.e.
axs.plot( ... , label="MRP")
When later calling axs.legend() a legend will automatically be generated with the respective labels. Ways to position the legend are detailed e.g. in this answer.
Here, you may want to place the legend in terms of figure coordinates, i.e.
ax.legend(loc="lower center",bbox_to_anchor=(0.5,0.8),bbox_transform=plt.gcf().transFigure)

Categories