Adding values to grouped bar chart in hvplot - holoviews

I'm trying to add labels to a grouped hvplot barchart.
My example dataframe has the following structure:
import pandas as pd
import numpy as np
import holoviews as hv
import hvplot.pandas
hv.extension('bokeh')
df = pd.DataFrame({'A' : ['A','B','A','B','A','B'],
'B' : [1,1,2,2,3,3],
'C' : list((range(20,26)))
})
The bar chart is created with the following code:
bar = df.hvplot.bar(x='B', y='C', by='A')
bar
hvplot bar chart
I tried to add labels according to this and this SO questions:
labels = hv.Labels(data=df, kdims=['B','A'],vdims='C')
labels
But an overlay of both plots
bar * labels
results in an error, though the dimensions seem to be the same for me.
ValueError: all the input arrays must have same number of dimensions
:Overlay
.Bars.I :Bars [B,A] (C)
.Labels.I :Labels [B,A] (C)
Any hint to the solution is appreciated. Thank you!

This is possible for normal bar charts, but unfortunately this is not possible yet for grouped barcharts: https://github.com/holoviz/holoviews/pull/3385
You could create separate bar charts per category in col A and then add labels, but you won't have a grouped bar chart then:
def barplot_and_labels_category(category):
df_subset = df[df.A == category]
plot = df_subset.hvplot.bar(x='B', y='C', ylim=(0, 30))
labels = hv.Labels(
df_subset,
kdims=['B', 'C'],
vdims='C',
).opts(text_color='black', text_font_size='20pt')
return plot * labels
(barplot_and_labels_category('A') + barplot_and_labels_category('B')).cols(1)

Related

Why don't different bars share the same y-axis in plotly.express group bars

I made these attached group bars with plotly.express:
And my code is like this:
import pandas as pd
df=pd.DataFrame([['C-1','C-11','111'],['C-1','C-12','121'],['C-1','C-13','31'],['C-1','C-14','120'],['C-1','C-15','100'],
['C-2','C-21','150'],['C-2','C-22','168'],['C-2','C-23','173'],['C-2','C-24','111'],['C-2','C-25','121'],
['C-3','C-31','123'],['C-3','C-32','100'],['C-3','C-33','111'],['C-3','C-34','111'],['C-3','C-35','163'],
['C-4','C-41','174'],['C-4','C-42','174'],['C-4','C-43','173'],['C-4','C-44','135'],['C-4','C-45','102'],
['C-5','C-51','118'],['C-5','C-52','122'],['C-5','C-53','113'],['C-5','C-54','178'],['C-5','C-55','142']
],columns=['Case group','Case number','Average revenue'])
import plotly.express as px
fig = px.bar(df,
x='Case number',y='Average revenue',
color='Case group', barmode='group',text="Average revenue",
height=500,width=700,
color_discrete_sequence=[px.colors.qualitative.Dark2[0],px.colors.qualitative.Set2[1],px.colors.qualitative.Pastel[0],px.colors.qualitative.Set2[5],px.colors.qualitative.Set2[4],])
)
fig.update_traces(textposition='outside')
fig.show()
My question is, how can I make the y-axis labels normally ascend so that all groups share the same y-axis scale? e.g., C-55 should be lower than C-54 in my ideal thought.
Also, the annotations above each bar are too small to read. I tried to change the font size with fig.update.layout(). but it didn't work.
Please let me know if I did anything wrong.
I would really appreciate any help you can provide.
You only need to convert the column type to float as follows:
df["Average revenue"] = df["Average revenue"].astype(float)
The full code:
import pandas as pd
import plotly.express as px
df=pd.DataFrame([['C-1','C-11','111'],['C-1','C-12','121'],['C-1','C-13','31'],['C-1','C-14','120'],['C-1','C-15','100'],
['C-2','C-21','150'],['C-2','C-22','168'],['C-2','C-23','173'],['C-2','C-24','111'],['C-2','C-25','121'],
['C-3','C-31','123'],['C-3','C-32','100'],['C-3','C-33','111'],['C-3','C-34','111'],['C-3','C-35','163'],
['C-4','C-41','174'],['C-4','C-42','174'],['C-4','C-43','173'],['C-4','C-44','135'],['C-4','C-45','102'],
['C-5','C-51','118'],['C-5','C-52','122'],['C-5','C-53','113'],['C-5','C-54','178'],['C-5','C-55','142']
],columns=['Case group','Case number','Average revenue'])
df["Average revenue"] = df["Average revenue"].astype(float)
fig = px.bar(df,
x="Case number",y='Average revenue',
color='Case group', barmode='group',text="Average revenue",
height=500,width=700,
color_discrete_sequence=[px.colors.qualitative.Dark2[0],px.colors.qualitative.Set2[1],px.colors.qualitative.Pastel[0],px.colors.qualitative.Set2[5],px.colors.qualitative.Set2[4],])
fig.update_traces(textposition='outside', width=0.8)
fig.show()
Output

How can I convert plot_trisurf-data correctly to plot_surface-data?

I am trying to plot 3d-coordinates from an array as a surface with plot_surface. My array contain x,y,z-data (each the same size). I have successfully managed to plot the data via the plot_trisurf function. This gives me the following plot:
plot_trisurf function
To add a fourth dimension in color I would like to plot the same surface with plot_surface. I have managed to get nearly the result I want with the following code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
points = np.loadtxt(...)
x,y,z = points[:,0],points[:,1],points[:,2]
X, Y = np.meshgrid(x,y)
Z = np.outer(z.T, np.ones(z.size))
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_surface(X,Y,Z,ls='None')
ax.set_xlabel('x - Axis in mm')
ax.set_ylabel('y - Axis in mm')
ax.set_zlabel('z - Axis in mm')
ax.set_xlim([-np.max((np.abs(points))), np.max((np.abs(points)))])
ax.set_ylim([-np.max((np.abs(points))), np.max((np.abs(points)))])
ax.set_zlim([-np.max((np.abs(points))), np.max((np.abs(points)))])
which gave me the following plot:
plot_surface-function, view 1
plot_surface-function, view 2
How do I manage to eliminate the connection between the base contact points on the ground?

How can i specify the color of a bar in a bar chart by its value?

Well, i created a bar chart and now i want to specify the color of a bar depending of its value on y-axis. simplified- if the value is positive the bar should be red and is the value nagative the bar should be blue.
For me it's only possible to change the color along the x-axis but not the y-axis.
from bokeh.palettes import plasma
source = ColumnDataSource(data={'date' : pd.to_datetime(df_data['date'], format='%Y-%m'), 'values' : df_data['values'], 'color' : plasma(256)})
p = figure(x_axis_label='time',
x_axis_type='datetime',
y_axis_label='diff',
tools = [hover]
toolbar_location=None
title="title")
p.vbar(x = 'date',top = 'values', source=source, width=timedelta(days=20), color = 'color')
I've found an example on:
https://docs.bokeh.org/en/latest/docs/user_guide/categorical.html
But i need to differentiate or to color the bars by their values no by their number. I know my example makes no sense, but i only want to demonstrate what my expectations are.
Ok, i found a solution by myself by unsing the cut function of pandas.
import pandas as pd
import numpy as np
values = array(df_data['values']).values)
bins = [np.NINF, 0, np.inf]
categories = pd.cut(values, bins, right=False)
palette = ['blue', 'red']
colors = []
for i in categories.codes:
colors.append[palette[i]]
# Now i can add this column to my ColumnDataSource:
source = ColumnDataSource(data={'date' : pd.to_datetime(df_data['date'], format='%Y-%m'), 'values' : df_data['values'], 'color' : colors}
p.vbar(x = 'date',top = 'values', source=source, width=timedelta(days=20), color = 'colors')
Of course this is just as "quick and dirty" solution and there is enough room for optimization.

matplotlib bar plot add legend from categories dataframe column

I try to add the legend which should, according to my example, output:
a red square with the word fruit and
a green square with the word
veggie.
I tried several things (the example below is just 1 of the many trials), but I can't get it work.
Can someone tell me how to solve this problem?
import pandas as pd
from matplotlib import pyplot as plt
data = [['apple', 'fruit', 10], ['nanaba', 'fruit', 15], ['salat','veggie', 144]]
data = pd.DataFrame(data, columns = ['Object', 'Type', 'Value'])
colors = {'fruit':'red', 'veggie':'green'}
c = data['Type'].apply(lambda x: colors[x])
bars = plt.bar(data['Object'], data['Value'], color=c, label=colors)
plt.legend()
The usual way to create a legend for objects which are not in the axes would be to create proxy artists as shown in the legend guide
Here,
colors = {'fruit':'red', 'veggie':'green'}
labels = list(colors.keys())
handles = [plt.Rectangle((0,0),1,1, color=colors[label]) for label in labels]
plt.legend(handles, labels)
So this is a hacky solution and I'm sure there are probably better ways to do this. What you can do is plot individual bar plots that are invisible using width=0 with the original plot colors and specify the labels. You will have to do this in a subplot though.
import pandas as pd
from matplotlib import pyplot as plt
data = [['apple', 'fruit', 10], ['nanaba', 'fruit', 15], ['salat','veggie', 144]]
data = pd.DataFrame(data, columns = ['Object', 'Type', 'Value'])
colors = {'fruit':'red', 'veggie':'green'}
c = data['Type'].apply(lambda x: colors[x])
ax = plt.subplot(111) #specify a subplot
bars = ax.bar(data['Object'], data['Value'], color=c) #Plot data on subplot axis
for i, j in colors.items(): #Loop over color dictionary
ax.bar(data['Object'], data['Value'],width=0,color=j,label=i) #Plot invisible bar graph but have the legends specified
ax.legend()
plt.show()

Linking HoloViews plots with Bokeh customizations

I'm struggling with some of the finer points of complex HoloViews plots, especially linked plots customizing the appearance of fonts and data points.
Using the following code, I can create this plot that has most of the features I want, but am stumped by a few things:
I want one marginal for the whole set of plots linked to 'ewr' (with individual marginals for each of the other axes), ideally on the left of the set; but my attempts to get just one in my definitions of s1 and s2 haven't worked, and I can find nothing in the documentation about moving a marginal to the left (or bottom for that matter).
I want to be able to define tooltips that use columns from my data that are not displayed in the plots. I can see one way of accomplishing this as shown in the commented alternate definition for s1, but that unlinks the plot it creates from the others. How do I create linked plots that have tooltips with elements not in those plots?
For reference, the data used is available here (converted in the code below to a Pandas dataframe, df).
import holoviews as hv
from holoviews import dim, opts
hv.extension('bokeh')
renderer = hv.renderer('bokeh')
from bokeh.models import HoverTool
from holoviews.plotting.links import DataLink
TOOLS="crosshair,pan,wheel_zoom,zoom_in,zoom_out,box_zoom,undo,redo,reset,tap,save,box_select,poly_select,lasso_select".split(",")
ht = HoverTool(
tooltips=[('Name', '#{name}'), ('EWR', '#{ewr}{%0.2f}'), ('Win Rate', '#{winrate}{%d}')],
formatters={'ewr' : 'printf', 'winrate' : 'printf'})
point_opts = opts.Scatter(fill_color='black', fill_alpha=0.1, line_width=1, line_color='gray', size=5, tools=TOOLS+[ht])
hist_opts = opts.Histogram(fill_color='gray', fill_alpha=0.9, line_width=1, line_color='gray', tools=['box_select'], labelled=[None, None])
#s1 = hv.Scatter(df[['kfai','ewr','name','winrate']]).hist(num_bins=51, dimension='kfai')
s1 = hv.Scatter(df, 'kfai','ewr').hist(num_bins=51, dimension='kfai')
s2 = hv.Scatter(df, 'aerc', 'ewr').hist(num_bins=51, dimension=['aerc',None])
s3 = hv.Scatter(df, 'winrate', 'ewr').hist(num_bins=51, dimension=['winrate','ewr'])
p = (s1 + s2 + s3).opts(point_opts, hist_opts, opts.Layout(shared_axes=True, shared_datasource=True))
renderer.save(p, '_testHV')

Resources