I am trying to save a holoviews plot, to png, using the bokeh backend. But the saved png has its xlabel partially cutoff. How do I increase the whitespace margin at the bottom of the plot to avoid this?
I have tried to create a single column NdLayout, and then edit the plot afterwards, but each plot has its xlabel trimmed.
When rendering this in a jupyter notebook, the xlabel appears as expected.
import numpy as np
import holoviews as hv
hv.extension('bokeh')
a1 = np.random.normal(0,1,size=600)
a2 = np.random.normal(1,1,size=600)
b = np.arange(0,600)
xx = hv.Curve([(ii,jj) for ii,jj in zip(b,a1)],['predicted_sample'],['Value']).relabel('StandardNormal')
yy = hv.Curve([(ii,jj) for ii,jj in zip(b,a2)],['predicted_sample'],['Value']).relabel('ShiftedNormal')
hv.save(xx*yy,"plot.png")
Single:
hv.save(((xx*yy) + (xx*yy)).cols(1),'ndlayout_plots.png')
NdLayout:
For those that find this, I found a work around using hooks and setting the bokeh min_border_bottom value directly.
import numpy as np
import holoviews as hv
hv.extension('bokeh')
a1 = np.random.normal(0,1,size=600)
a2 = np.random.normal(1,1,size=600)
b = np.arange(0,600)
xx = hv.Curve([(ii,jj) for ii,jj in zip(b,a1)],['predicted_sample'],['Value']).relabel('StandardNormal')
yy = hv.Curve([(ii,jj) for ii,jj in zip(b,a2)],['predicted_sample'],['Value']).relabel('ShiftedNormal')
pp = xx*yy #create the overlay
def fixBottomMargin(plot,element):
plot.handles['plot'].min_border_bottom = 100
pp = pp.opts(opts.Curve(hooks=[fixBottomMargin])) #call the hooks on the curve element
hv.save(pp,filename='plot.png')
Information found by reading the bokeh documentation, Styling Visual Attributes
Related
I have been using geopandas, but I am trying to switch to geoviews because it is more interactive. I'm wondering how to specify the axis limits for plotted data as a default view. I understand that it will always plot all of the data that exist, but it would be nice to have a given zoom for the purpose of this project. I posted the image of the map output below. However, I want it to output with xlim = ([-127, -102]) and ylim = ([25, 44]). I looked on stackoverflow and other places online and was unable to find the answer.
# Read in shapefiles
fire = pd.read_pickle(r'fire_Aug2020.pkl')
fire = fire.loc[fire['FRP'] != -999.0, :]
# Assign gv.Image
data = gv.Dataset(fire[['Lon','Lat','YearDay']])
points = data.to(gv.Points, ['Lon','Lat'])
m = (points).opts(tools = ['hover'], width = 400, height = 200)
m
Your are very close to a working solution. Try to add xlim and ylim as tuple to the opts call.
Minimal Example
Comment: GeoViews is based on Holoviews, see the documentation for more details.
Because GeoViews is not installed on my machine, the example below uses HoloViews. I will update this answer soon.
import pandas as pd
import numpy as np
import numpy as np
import holoviews as hv
from holoviews import opts
hv.extension('bokeh')
fire = pd.DataFrame({'Lat':np.random.randint(10,80,50),'Lon':np.random.randint(-160,-60,50)})
data = hv.Dataset(fire[['Lon','Lat']])
points = data.to(hv.Points, ['Lon','Lat'])
(points).opts(tools = ['hover'], width = 400)
Above is the output without limits and below I make use of xlim and ylim.
(points).opts(tools = ['hover'], width = 400, height = 200, xlim=(-127,-102), ylim=(25,44))
I am trying to plot my MultiIndex Xarray in jupyter using Matplotlib and Holoviews. I can plot a very basic plot using matplotlib but I get errors otherwise.
My xarray is this -
I am using this code to plot my spectrogram with matpllotlib and some in-house function of xarray to find the max value in the matrix that I want to plot.
# Plotting in some other way
plt.figure(figsize=(3,5))
data_slice = temp1
max_value = np.log(temp1.max(xr.ALL_DIMS)['__xarray_dataarray_variable__'].values)
xr.ufuncs.log(data_slice).plot(cmap='magma', vmin=0, vmax = max_value*.7)
In this code I get the error - KeyError: 'xarray_dataarray_variable'
When I am plotting the spectrogram using holoviews I use this code -
# plotting the new xarray that we got - 2 dimenntional
# making an array that represents the freq bins
final_freqs = np.linspace(0, 125000, 257)
time_to_see = 10
time_stamps_to_be_displayed = [[] for _ in range(165)]
for x in range(0, 55):
# multiplying it by 0.01 to get it to seconds as each window is for 10 miliseconds.
time_stamps_to_be_displayed[x].append(time_to_see + x * 0.005)
time_displayed = np.array(time_stamps_to_be_displayed).flatten()
xr_spec = xr.DataArray(temp1, dims = ('freq','time') ,coords = {'freq':final_freqs,'time':time_displayed})
xr_spec.name = 'Spectrogram'
# plotting the graph
import holoviews as hv
from holoviews import opts
hv.extension('bokeh', 'matplotlib')
import os
os.environ['HV_DOC_HTML'] = 'true'
#%env HV_DOC_HTML=true
import numpy as np
from bokeh.plotting import figure, show
from bokeh.io import output_notebook
hv.extension('bokeh')
output_notebook()
import imp
imp.reload(hv)
hv_spec = hv.Dataset(xr_spec)
hv.extension('bokeh')
hv_spec.to(hv.Image, ['time', 'freq'])
In this, I get the error - unsupported operand type(s) for -: 'list' and 'list' for the very last line.
What am I doing wrong? Please help me.
StackTrace is here -
I have several histograms that I succeded to plot using plotly like this:
fig.add_trace(go.Histogram(x=np.array(data[key]), name=self.labels[i]))
I would like to create something like this 3D stacked histogram but with the difference that each 2D histogram inside is a true histogram and not just a hardcoded line (my data is of the form [0.5 0.4 0.5 0.7 0.4] so using Histogram directly is very convenient)
Note that what I am asking is not similar to this and therefore also not the same as this. In the matplotlib example, the data is presented directly in a 2D array so the histogram is the 3rd dimension. In my case, I wanted to feed a function with many already computed histograms.
The snippet below takes care of both binning and formatting of the figure so that it appears as a stacked 3D chart using multiple traces of go.Scatter3D and np.Histogram.
The input is a dataframe with random numbers using np.random.normal(50, 5, size=(300, 4))
We can talk more about the other details if this is something you can use:
Plot 1: Angle 1
Plot 2: Angle 2
Complete code:
# imports
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
pio.renderers.default = 'browser'
# data
np.random.seed(123)
df = pd.DataFrame(np.random.normal(50, 5, size=(300, 4)), columns=list('ABCD'))
# plotly setup
fig=go.Figure()
# data binning and traces
for i, col in enumerate(df.columns):
a0=np.histogram(df[col], bins=10, density=False)[0].tolist()
a0=np.repeat(a0,2).tolist()
a0.insert(0,0)
a0.pop()
a1=np.histogram(df[col], bins=10, density=False)[1].tolist()
a1=np.repeat(a1,2)
fig.add_traces(go.Scatter3d(x=[i]*len(a0), y=a1, z=a0,
mode='lines',
name=col
)
)
fig.show()
Unfortunately you can't use go.Histogram in a 3D space so you should use an alternative way. I used go.Scatter3d and I wanted to use the option to fill line doc but there is an evident bug see
import numpy as np
import plotly.graph_objs as go
# random mat
m = 6
n = 5
mat = np.random.uniform(size=(m,n)).round(1)
# we want to have the number repeated
mat = mat.repeat(2).reshape(m, n*2)
# and finally plot
x = np.arange(2*n)
y = np.ones(2*n)
fig = go.Figure()
for i in range(m):
fig.add_trace(go.Scatter3d(x=x,
y=y*i,
z=mat[i,:],
mode="lines",
# surfaceaxis=1 # bug
)
)
fig.show()
I am trying to use the holoviews Rangetool link in a holoviews Overlayed plot. But unable to achieve the range linking to work. Is it possible to achieve this.?
Based on these links example 1 and example 2 I tried the options with an overlayed plot instead of a single curve plot. But this didn't work. Below I provided an example with a similar dummy data.
import pandas as pd
import holoviews as hv
from holoviews import opts
import numpy as np
from holoviews.plotting.links import RangeToolLink
hv.extension('bokeh')
# Genrate Random Data
def randomDataGenerator(noOfSampleDataSets):
for i in range(noOfSampleDataSets):
res = np.random.randn(1000).cumsum()
yield res
# Overlay Plots
overlaid_plot = hv.Overlay([hv.Curve(data)
.opts(width=800, height=600, axiswise=True, default_tools=[])
for data in randomDataGenerator(5)])
# Adjust Source Height
source = overlaid_plot.opts(height=200)
# adjust target plot attributes
target = source.opts(clone=True, width=800, labelled=['y'],)
# Link source and target
rtlink = RangeToolLink(source, target)
# Compose and plot.
(target + source).cols(1).opts(merge_tools=False)
I expect that the source plot will show up with a range tool as shown in the example and be able to select a range in it which should select the same data points in the target plot.
Following code works in my case. I slightly refactored the code. But the logic is still the same. So if we have a an overlaid plot, link one of the curves in the overlayed plot works fine with all the remaining curves.
Following code works in a jupyter notebook. Its not tested in other environment.
import holoviews as hv
import numpy as np
hv.extension('bokeh')
from holoviews.plotting.links import RangeToolLink
# Genrate Random Data
def randomDataGenerator(noOfSampleDataSets):
for i in range(noOfSampleDataSets):
res = np.random.randn(1000).cumsum()
yield res
#generate all curves
def getCurves(n):
for data in randomDataGenerator(n):
curve = hv.Curve(data)
yield curve
source_curves, target_curves = [], []
for curve in getCurves(10):
# Without relabel, the curve somehow shares the ranging properties. opts with clone=True doesn't help either.
src = curve.relabel('').opts(width=800, height=200, yaxis=None, default_tools=[])
tgt = curve.opts(width=800, labelled=['y'], toolbar='disable')
source_curves.append(src)
target_curves.append(tgt)
# link RangeTool for the first curves in the list.
RangeToolLink(source_curves[0],target_curves[0])
#Overlay the source and target curves
overlaid_plot_src = hv.Overlay(source_curves).relabel('Source')
overlaid_plot_tgt = hv.Overlay(target_curves).relabel('Target').opts(height=600)
# layout the plot and render
layout = (overlaid_plot_tgt + overlaid_plot_src).cols(1)
layout.opts(merge_tools=False,shared_axes=False)
In Bokeh I am able to add a text annotation to each point in my plot programmatically by using LabelSet. Below I give an example for a simple bar plot:
import numpy as np
import pandas as pd
# Make some data
dat = \
(pd.DataFrame({'team':['a','b','c'], 'n_people':[10,5,12]})
.assign(n_people_percent = lambda x: (x['n_people']/np.sum(x['n_people'])*100)
.round(1).astype('string') + '%')
)
dat
# Bar plot with text annotations for every bar
from bkcharts import show, Bar
from bkcharts.attributes import CatAttr
from bokeh.models import (ColumnDataSource, LabelSet)
source_labs = ColumnDataSource(data = dat)
p = Bar(data = dat, label = CatAttr(columns = 'team'), values = 'n_people')
labels = LabelSet(x = 'team', y = 'n_people',
text = 'n_people_percent', source = source_labs)
p.add_layout(labels)
show(p)
However I am not sure how to achieve the same thing with Holoviews. I can make the same bar plot without the annotations very easily:
import holoviews as hv
hv.extension('bokeh')
p = hv.Bars(dat, kdims=['team'], vdims=['n_people'])
p
I can add a single text label adding an Overlay with the hv.Text element
p * hv.Text('a', 11, '37.0%')
But I have no idea how I can label each bar without explicitly calling hv.Text separately for every data point (bar). The problem seems to be that hv.Text does not accept a data argument like other elements e.g. hv.Bars, instead just x and y coordinates. My intuition would be that I should be able to do something like
p * hv.Text(dat, kdims=['team'], vdims=['n_people_percent'])
Any help with this appreciated!
Looks like this commit adds vectorized labels to hv.Labels, so try:
import holoviews as hv
hv.extension('bokeh')
p = hv.Bars(dat, kdims=['team'], vdims=['n_people'])
p * hv.Labels(dat, kdims=['team', 'n_people'], vdims=['n_people_percent'])