deleting line from figure in bokeh - python

I am new to Bokeh. I made a widget where when I click a checkbox I want to be able to add/delete a line in a bokeh figure. I have 20 such checkboxes and I dont want to replot the whole figure, just to delete 1 line if a checkbox was unchecked.
This is done through a callback, where I have access to the figure object. I would imagine there is a way to do something like this:
F=figure()
F.line('x', 'y', source=source, name='line1')
F.line('x', 'z', source=source, name='line2')
%%in callback
selected_line_name = 'line1' # this would be determined by checkbox
selected_line = F.children[selected_line_name]
delete(selected_line)
However, I am unable to figure out how to
1) access a glyph from its parent object
2) delete a glyph
I tried setting the datasource 'y'=[], but since all column data sources have to be the same size, this removes all the plots...

There are several ways:
# Keep the glyphs in a variable:
line2 = F.line('x', 'z', source=source, name='line2')
# or get the glyph from the Figure:
line2 = F.select_one({'name': 'line2'})
# in callback:
line2.visible = False

This will work to maintain a shared 'x' data source column if glyphs are assigned as a variable and given a name attribute. The remove function fills the appropriate 'y' columns with nans, and the restore function replaces nans with the original values.
The functions require numpy and bokeh GlyphRenderer imports. I'm not sure that this method is worthwhile given the simple visible on/off option, but I am posting it anyway just in case this helps in some other use case.
Glyphs to remove or restore are referenced by glyph name(s), contained within a list.
src_dict = source.data.copy()
def remove_glyphs(figure, glyph_name_list):
renderers = figure.select(dict(type=GlyphRenderer))
for r in renderers:
if r.name in glyph_name_list:
col = r.glyph.y
r.data_source.data[col] = [np.nan] * len(r.data_source.data[col])
def restore_glyphs(figure, src_dict, glyph_name_list):
renderers = figure.select(dict(type=GlyphRenderer))
for r in renderers:
if r.name in glyph_name_list:
col = r.glyph.y
r.data_source.data[col] = src_dict[col]
Example:
from bokeh.plotting import figure, show
from bokeh.io import output_notebook
from bokeh.models import Range1d, ColumnDataSource
from bokeh.models.renderers import GlyphRenderer
import numpy as np
output_notebook()
p = figure(plot_width=200, plot_height=150,
x_range=Range1d(0, 6),
y_range=Range1d(0, 10),
toolbar_location=None)
source = ColumnDataSource(data=dict(x=[1, 3, 5],
y1=[1, 1, 2],
y2=[1, 2, 6],
y3=[1, 3, 9]))
src_dict = source.data.copy()
line1 = p.line('x', 'y1',
source=source,
color='blue',
name='g1',
line_width=3)
line2 = p.line('x', 'y2',
source=source,
color='red',
name='g2',
line_width=3)
line3 = p.line('x', 'y3',
source=source,
color='green',
name='g3',
line_width=3)
print(source.data)
show(p)
out:
{'x': [1, 3, 5], 'y1': [1, 1, 2], 'y2': [1, 2, 6], 'y3': [1, 3, 9]}
remove_glyphs(p, ['g1', 'g2'])
print(source.data)
show(p)
out:
{'x': [1, 3, 5], 'y1': [nan, nan, nan], 'y2': [nan, nan, nan], 'y3': [1, 3, 9]}
restore_glyphs(p, src_dict, ['g1', 'g3'])
print(source.data)
show(p)
('g3' was already on the plot, and is not affected)
out:
{'x': [1, 3, 5], 'y1': [1, 1, 2], 'y2': [nan, nan, nan], 'y3': [1, 3, 9]}
restore_glyphs(p, src_dict, ['g2'])
print(source.data)
show(p)
out:
{'x': [1, 3, 5], 'y1': [1, 1, 2], 'y2': [1, 2, 6], 'y3': [1, 3, 9]}

Related

Plotly: How to change variable/label names for the legend in a plotly express line chart?

I want to change the variable/label names in plotly express in python. I first create a plot:
import pandas as pd
import plotly.express as px
d = {'col1': [1, 2, 3], 'col2': [3, 4, 5]}
df = pd.DataFrame(data=d)
fig = px.line(df, x=df.index, y=['col1', 'col2'])
fig.show()
Which yields:
I want to change the label names from col1 to hello and from col2 to hi. I have tried using labels in the figure, but I cannot get it to work:
fig = px.line(df, x=df.index, y=['col1', 'col2'], labels={'col1': "hello", 'col2': "hi"})
fig.show()
But this seems to do nothing, while not producing an error. Obviously I could achieve my goals by changing the column names, but the actual plot i'm trying to create doesn't really allow for that since it comes from several different dataframes.
The answer:
Without changing the data source, a complete replacement of names both in the legend, legendgroup and hovertemplate will require:
newnames = {'col1':'hello', 'col2': 'hi'}
fig.for_each_trace(lambda t: t.update(name = newnames[t.name],
legendgroup = newnames[t.name],
hovertemplate = t.hovertemplate.replace(t.name, newnames[t.name])
)
)
Plot:
The details:
Using
fig.for_each_trace(lambda t: t.update(name = newnames[t.name]))
...you can change the names in the legend without ghanging the source by using a dict
newnames = {'col1':'hello', 'col2': 'hi'}
...and map new names to the existing col1 and col2 in the following part of the figure structure (for your first trace, col1):
{'hovertemplate': 'variable=col1<br>index=%{x}<br>value=%{y}<extra></extra>',
'legendgroup': 'col1',
'line': {'color': '#636efa', 'dash': 'solid'},
'mode': 'lines',
'name': 'hello', # <============================= here!
'orientation': 'v',
'showlegend': True,
'type': 'scatter',
'x': array([0, 1, 2], dtype=int64),
'xaxis': 'x',
'y': array([1, 2, 3], dtype=int64),
'yaxis': 'y'},
But as you can see, this doesn't do anything with 'legendgroup': 'col1', nor 'hovertemplate': 'variable=col1<br>index=%{x}<br>value=%{y}<extra></extra>' And depending on the complexity of your figure, this can pose a problem. So I would add legendgroup = newnames[t.name] and hovertemplate = t.hovertemplate.replace(t.name, newnames[t.name])into the mix.
Complete code:
import pandas as pd
import plotly.express as px
from itertools import cycle
d = {'col1': [1, 2, 3], 'col2': [3, 4, 5]}
df = pd.DataFrame(data=d)
fig = px.line(df, x=df.index, y=['col1', 'col2'])
newnames = {'col1':'hello', 'col2': 'hi'}
fig.for_each_trace(lambda t: t.update(name = newnames[t.name],
legendgroup = newnames[t.name],
hovertemplate = t.hovertemplate.replace(t.name, newnames[t.name])
)
)
Add the "name" parameter: go.Scatter(name=...)
Source https://plotly.com/python/figure-labels/
fig = go.Figure()
fig.add_trace(go.Scatter(
x=[0, 1, 2, 3, 4, 5, 6, 7, 8],
y=[0, 1, 2, 3, 4, 5, 6, 7, 8],
name="Name of Trace 1" # this sets its legend entry
))
fig.add_trace(go.Scatter(
x=[0, 1, 2, 3, 4, 5, 6, 7, 8],
y=[1, 0, 3, 2, 5, 4, 7, 6, 8],
name="Name of Trace 2"
))
fig.update_layout(
title="Plot Title",
xaxis_title="X Axis Title",
yaxis_title="X Axis Title",
legend_title="Legend Title",
font=dict(
family="Courier New, monospace",
size=18,
color="RebeccaPurple"
)
)
fig.show()
This piece of code is more concise.
import pandas as pd
import plotly.express as px
df = pd.DataFrame(data={'col1': [1, 2, 3], 'col2': [3, 4, 5]})
series_names = ["hello", "hi"]
fig = px.line(data_frame=df)
for idx, name in enumerate(series_names):
fig.data[idx].name = name
fig.data[idx].hovertemplate = name
fig.show()
If you're looking for something even more concise, this function does the job-
def custom_legend_name(new_names):
for i, new_name in enumerate(new_names):
fig.data[i].name = new_name
Then before fig.show(), just pass a list consisting of the names you want, to the function, like this custom_legend_name(['hello', 'hi'])
Here's what the complete code would look like-
def custom_legend_name(new_names):
for i, new_name in enumerate(new_names):
fig.data[i].name = new_name
import pandas as pd
import plotly.express as px
d = {'col1': [1, 2, 3], 'col2': [3, 4, 5]}
df = pd.DataFrame(data=d)
fig = px.line(df, x=df.index, y=['col1', 'col2'])
custom_legend_name(['hello','hi'])
fig.show()

Changing plot marker for every element inside the plot

I have a matrix that i am using scatter plot to visualize it of size 800x2. I am trying to change the marker type for every 100th element, for instance from 0 to 99 markers would be 'x' from 100 to 199 markers would be 'o' and so forth.
However i get the following error:
TypeError: only integer scalar arrays can be converted to a scalar
index
This is my actual code:
from matplotlib.pyplot import figure
import numpy as np
color=['b','r']
markers = ['x', 'o', '1', '.', '2', '>', 'D', 'v']
X_lda_colors= [ color[i] for i in list(np.array(y)%8) ]
X_lda_markers= [ markers[i] for i in list(np.array(y)%2) ]
plt.xlabel('1-eigenvector')
plt.ylabel('2-eigenvector')
for i in range(X_lda.shape[0]):
plt.scatter(
X_lda[i,0],
X_lda[i,1],
c=X_lda_colors[i],
marker=X_lda_markers[i],
cmap='rainbow',
alpha=0.7,
edgecolors='w')
plt.show()
My goal is to basically use any sort of marker to differentiate between every 100th element inside my x_lda[i, 1] label that are clusters being plotted. This code used to work following this question: Plotting different clusters markers for every class in scatter plot.
But for my case, it gives me the error described above.
Here's a reproducible example:
X_lda = np.asarray([([1, 2], [1,5], [2, 3],[3, 5], [3, 4], [6, 9], [7, 9], [7, 8], [7, 10], [7, 12], [13, 14], [15, 16], [12, 14], [13, 15], [12, 14], [14, 14], [13, 4], [12, 5], [13, 4], [13, 3], [12, 6])]).reshape(21, 2)
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
from matplotlib.pyplot import figure
plt.xlabel('LD1')
plt.ylabel('LD2')
plt.scatter(
X_lda[:,0],
X_lda[:,1],
c=['red', 'red', 'red', 'red', 'red', 'red', 'red', 'red', 'red', 'red', 'green', 'green', 'green', 'green', 'green', 'green', 'green', 'green', 'green', 'green', 'green'],
cmap='rainbow',
alpha=0.7,
edgecolors='w'
)
For this 21x2 array, i'd like for change the first 7 elements to 'x', next 7 elements to 'o', and the last 7 elements to '>' for instance.
I think you may be looking for something like this, exchanging row 5 and 6 in your code above:
X_lda_colors= [ color[i%2] for i in range(X_lda.shape[0]) ]
X_lda_markers= [ markers[i%8] for i in range(X_lda.shape[0]) ]
However, you should not loop throughout all of your 800 points and create one plot each. A workaround would be something like this:
# plot each point in blue
plt.scatter(
X_lda[:,0], X_lda[:,1]
c = "b",
...
)
# plot again using every 100th element in red
plt.scatter(
X_lda[::100,0], X_lda[::100,1]
c = "r",
...
)
this will overprint each 100th element with a second dot in red. You end up having only two plot objects with 800 and 8 points respectively.

How to create conditional coloring for matplotlib table values?

How do I add conditional coloring to this table?
import pandas as pd
import matplotlib.pyplot as plt
df = pd.DataFrame({'A':[16, 15, 14, 16],
'B': [3, -2, 5, 0],
'C': [200000, 3, 6, 800000],
'D': [51, -6, 3, 2]})
fig, ax = plt.subplots(figsize=(10,5))
ax.axis('tight')
ax.axis('off')
the_table = ax.table(cellText = df.values, colLabels = df.columns, loc='center')
plt.show()
How do I add conditional coloring to the table where column A and column D values are greater than or equal to 15, the cells are red; else they're green. If column B and column C values are greater than or equal to 5, the cells are red; else they're green. This is what it should look like:
Generate a list of lists and feed it to cellColours. Make sure that the list of lists contains as many lists as you have rows in the data frame and each of the lists within the list of lists contains as many strings as you have columns in the data frame.
import pandas as pd
import matplotlib.pyplot as plt
df = pd.DataFrame({'A':[16, 15, 14, 16],
'B': [3, -2, 5, 0],
'C': [200000, 3, 6, 800000],
'D': [51, -6, 3, 2]})
colors = []
for _, row in df.iterrows():
colors_in_column = ["g", "g", "g", "g"]
if row["A"]>=15:
colors_in_column[0] = "r"
if row["B"]>=5:
colors_in_column[1] = "r"
if row["C"]>5:
colors_in_column[2] = "r"
if row["D"]>=15:
colors_in_column[3] = "r"
colors.append(colors_in_column)
fig, ax = plt.subplots(figsize=(10,5))
ax.axis('tight')
ax.axis('off')
the_table = ax.table(cellText = df.values, colLabels = df.columns, loc='center', cellColours=colors)
plt.show()

how to plot a histogram by given points in python 3

I have 60 numbers divided into 8 intervals:
[[534, 540.0, 3], [540.0, 546.0, 3], [546.0, 552.0, 14], [552.0, 558.0, 8], [558.0, 564.0, 14], [564.0, 570.0, 9], [570.0, 576.0, 6], [576.0, 582.0, 3]]
The number of numbers in each interval is divided by 6:
[0.5, 0.5, 2.33, 1.33, 2.33, 1.5, 1.0, 0.5]
How do I create a histogram so that the height of the bars corresponds to the obtained values, while signing the intervals in accordance with my intervals? The result should be something like this
i do not have reputation to post images, so
Running F Blanchet's code generates the following graph in my IPython console:
That doesn't really look like your image. I think you're looking for something more like this, where the x-ticks are between the bars:
This is the code I used to generate the above plot:
import matplotlib.pyplot as plt
# Include one more value for final x-tick.
intervals = list(range(534, 583, 6))
# Include one more bar height that == 0.
bar_height = [0.5, 0.5, 2.33, 1.33, 2.33, 1.5, 1.0, 0.5, 0]
plt.bar(intervals,
bar_height,
width = [6] * 8 + [0], # Set width of 0 bar to 0.
align = "edge", # Align ticks at edge of bars.
tick_label = intervals) # Make tick labels explicit.
You can use matplotlib :
import matplotlib.pyplot as plt
data = [[534, 540.0, 3], [540.0, 546.0, 3], [546.0, 552.0, 14], [552.0, 558.0, 8], [558.0, 564.0, 14], [564.0, 570.0, 9], [570.0, 576.0, 6], [576.0, 582.0, 3]]
x = [element[0]+3 for element in data]
y = [element[2]/6 for element in data]
width = 6
plt.bar(x, y, width, color="blue")
plt.show()
More documentation here

Cufflinks(Plotly) can't plot date time but numbers on the x-axis for the heatmap

I tried to plot a heatmap using Cufflinks API and i changed my pandas dataframe's index to datetime
but the x-axis shows scientific numbers rather than dates:
A solution may be to specify the x-axis tick values explicitly from your pandas index.
For example:
import pandas as pd
import plotly.graph_objs as go
import cufflinks as cf
df = pd.DataFrame({'C1': [0, 1, 2, 3], 'C2': [1, 2, 3, 4], 'C3': [2, 3, 4, 5], 'C4': [3, 4, 5, 6]},
index=pd.to_datetime(['2016-11', '2016-12', '2017-01', '2017-02']))
# set the tick values to the index labels directly
layout = go.Layout(xaxis = go.XAxis(
ticks=df.index.tolist(),
showticklabels=True,
linewidth=1,
tickvals=df.index.tolist()
))
cf.go_offline()
df.iplot(kind='heatmap', fill=True, colorscale='spectral', filename='cufflinks/test', layout=layout)
This produces the plot
I arrived here attempting to resolve a similar issue to yours. This post is a little late, but perhaps this will also help future readers.

Categories

Resources