Python Bokeh: Plotting same chart multiple times in gridplot - python

I'm currently trying to get an overview of plots of data of different dates. To get a good feeling of the data I would like to plot relevant plots next to each other. This means I want to use the same plot multiple times in the gridplot command. However what I noticed is that when i use the same chart multiple times it will only show it once in the final .html file. My first attempt at solving this was to use a copy.deepcopy for the charts, but this gave the following error:
RuntimeError: Cannot get a property value 'label' from a LineGlyph instance before HasProps.__init__
My approach has been as follows:
from bokeh.charts import Line, output_file, show, gridplot
import pandas as pd
output_file('test.html')
plots = []
df = pd.DataFrame([[1,2], [3,1], [2,2]])
print(df)
df.columns = ['x', 'y']
for i in range(10):
plots.append(Line(df, x='x', y='y', title='Forecast: ' + str(i),
plot_width=250, plot_height=250))
plot_matrix = []
for i in range(len(plots)-1, 2, -1):
plot_matrix.append([plots[i-3], plots[i-2], plots[i]])
p = gridplot(plot_matrix)
show(p)
The results of which is a an html page with a grid plot with a lot of missing graphs. Each graph is exactly shown once (instead of the 3 times required), which leads me to think that the gridplot does not like me using the same object multiple times. An obvious solve is to simply create every graph 3 times as a different object, which I will do for now, but not only is this inefficient, it also hurts my eyes when looking at my code. I'm hoping somebody has a more elegant solution for my problem.
EDIT: made code runable

This is not possible. Bokeh plots (or Bokeh objects in general) may not be re-used in layouts.

Related

Why am I unable to make a plot containing subplots in plotly using a px.scatter plot?

I have been trying to make a figure using plotly that combines multiple figures together. In order to do this, I have been trying to use the make_subplots function, but I have found it very difficult to have the plots added in such a way that they are properly formatted. I can currently make singular plots (as seen directly below):
However, whenever I try to combine these singular plots using make_subplots, I end up with this:
This figure has the subplots set up completely wrong, since I need each of the four subplots to contain data pertaining to the four methods (A, B, C, and D). In other words, I would like to have four subplots that look like my singular plot example above.
I have set up the code in the following way:
for sequence in sequences:
#process for making sequence profile is done here
sequence_df = pd.DataFrame(sequence_profile)
row_number=1
grand_figure = make_subplots(rows=4, cols=1)
#there are four groups per sequence, so the grand figure should have four subplots in total
for group in sequence_df["group"].unique():
figure_df_group = sequence_df[(sequence_df["group"]==group)]
figure_df_group.sort_values("sample", ascending=True, inplace=True)
figure = px.line(figure_df_group, x = figure_df_group["sample"], y = figure_df_group["intensity"], color= figure_df_group["method"])
figure.update_xaxes(title= "sample")
figure.update_traces(mode='markers+lines')
#note: the next line fails, since data must be extracted from the figure, hence why it is commented out
#grand_figure.append_trace(figure, row = row_number, col=1)
figure.update_layout(title_text="{} Profile Plot".format(sequence))
grand_figure.append_trace(figure.data[0], row = row_number, col=1)
row_number+=1
figure.write_image(os.path.join(output_directory+"{}_profile_plot_subplots_in_{}.jpg".format(sequence, group)))
grand_figure.write_image(os.path.join(output_directory+"grand_figure_{}_profile_plot_subplots.jpg".format(sequence)))
I have tried following directions (like for example, here: ValueError: Invalid element(s) received for the 'data' property) but I was unable to get my figures added as is as subplots. At first it seemed like I needed to use the graph object (go) module in plotly (https://plotly.com/python/subplots/), but I would really like to keep the formatting/design of my current singular plot. I just want the plots to be conglomerated in groups of four. However, when I try to add the subplots like I currently do, I need to use the data property of the figure, which causes the design of my scatter plot to be completely messed up. Any help for how I can ameliorate this problem would be great.
Ok, so I found a solution here. Rather than using the make_subplots function, I just instead exported all the figures onto an .html file (Plotly saving multiple plots into a single html) and then converted it into an image (HTML to IMAGE using Python). This isn't exactly the approach I would have preferred to have, but it does work.
UPDATE
I have found that plotly express offers another solution, as the px.line object has the parameter of facet that allows one to set up multiple subplots within their plot. My code is set up like this, and is different from the code above in that the dataframe does not need to be iterated in a for loop based on its groups:
sequence_df = pd.DataFrame(sequence_profile)
figure = px.line(sequence_df, x = sequence_df["sample"], y = sequence_df["intensity"], color= sequence_df["method"], facet_col= sequence_df["group"])
Although it still needs more formatting, my plot now looks like this, which is works much better for my purposes:

Grouped bar chart of multiindex

first of all: I'm completely new to python.
I'm trying to visualize some measured data. Each entry has a quadrant, number and sector. The original data lies in a .xlsx file. I've managed to use a .pivot_table to sort the data according to its sector. Due to overlapping, number and quadrant also have to be indexed. Now I want to plot it as a bar chart, where the bars are grouped by sector and the colors represent the quadrant.
But because number also has to be indexed, it shows up in the bar chart as a separate group. There should only be three groups, 0, i and a.
MWE:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
d = {'quadrant': ["0","0","0","0","0","0","I","I","I","I","I","I","I","I","I","I","I","I","II","II","II","II","II","II","II","II","II","II","II","II","III","III","III","III","III","III","III","III","III","III","III","III","IV","IV","IV","IV","IV","IV","IV","IV","IV","IV","IV","IV"], 'sector': [0,"0","0","0","0","0","a","a","a","a","a","a","i","i","i","i","i","i","a","a","a","a","a","a","i","i","i","i","i","i","a","a","a","a","a","a","i","i","i","i","i","i","a","a","a","a","a","a","i","i","i","i","i","i"], 'number': [1,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6], 'Rz_m': [67.90,44.17,44.30,63.43,49.87,39.33,61.17,69.37,66.20,44.20,64.77,39.93,44.33,50.97,55.90,51.33,58.23,44.53,50.03,47.40,58.67,71.57,57.60,70.77,63.93,47.37,46.90,34.73,41.27,48.23,58.30,47.07,50.53,51.20,32.67,50.37,37.50,55.50,41.20,48.07,56.80,49.77,40.87,44.43,44.00,60.03,63.73,72.80,51.60,45.53,60.27,71.00,59.63,48.70]}
df = pd.DataFrame(data=d)
B = df.pivot_table(index=['sector','number', 'quadrant'])
B.unstack().plot.bar(y='Rz_m')
The data viz ecosystem in Python is pretty diverse and there are multiple libraries you can use to produce the same chart. Matplotlib is a very powerful library, but it's also quite low-level, meaning you often have to do a lot of preparatory work before getting to the chart, so usually you'll find people use seaborn for static visualisations, especially if there is a scientific element to them (it has built-in support for things like error bars, etc.)
Out of the box, it has a lot of chart types to support exploratory data analysis and is built on top of matplotlib. For your example, if I understood it right, it would be as simple as:
import seaborn as sns
sns.catplot(x="sector", y="Rz_m", hue="quadrant", data=df, ci=None,
height=6, kind="bar", palette="muted")
And the output would look like this:
Note that in your example, you missed out "" for one of the zeroes and 0 and "0" are plotted as separate columns. If you're using seaborn, you don't need to pivot the data, just feed it the df as you've defined it.
For interactive visualisations (with tooltips, zoom, pan, etc.), you can also check out bokeh.
There is an interesting wrinkle to this - how to center the nested bars on the label. By default the bars are drawn with center alignment which works fine for an odd number of columns. However, for an even number, you'd want them to be centered on the right edge. You can make a small alteration in the source code categorical.py, lines beginning 1642 like so:
# Draw the bars
offpos = barpos + self.hue_offsets[j]
barfunc(offpos, self.statistic[:, j], -self.nested_width,
color=self.colors[j], align="edge",
label=hue_level, **kws)
Save the .png and then change it back, but it's not ideal. Probably worth flagging up to the library maintainers.

How can I loop through a list of elements and create time series plots in Python

Here is a sample of the data I'm working with WellAnalyticalData I'd like to loop through each well name and create a time series chart for each parameter with sample date on the x-axis and the value on the y-axis. I don't think I want subplots, I'm just looking for individual plots of each analyte for each well. I've used pandas to try grouping by well name and then attempting to plot, but that doesn't seem to be the way to go. I'm fairly new to python and I think I'm also having trouble figuring out how to construct the loop statement. I'm running python 3.x and am using the matplotlib library to generate the plots.
so if I understand your question correctly you want one plot for each combination of Well and Parameter. No subplots, just a new plot for each combination. Each plot should have SampleDate on the x-axis and Value on the y-axis. I've written a loop here that does just that, although you'll see that since in your data has just one date per well per parameter, the plots are just a single dot.
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
df = pd.DataFrame({'WellName':['A','A','A','A','B','B','C','C','C'],
'SampleDate':['2018-02-15','2018-03-31','2018-06-07','2018-11-14','2018-02-15','2018-11-14','2018-02-15','2018-03-31','2018-11-14'],
'Parameter':['Arsenic','Lead','Iron','Magnesium','Arsenic','Iron','Arsenic','Lead','Magnesium'],
'Value':[0.2,1.6,0.05,3,0.3,0.79,0.3,2.7,2.8]
})
for well in df.WellName.unique():
temp1 = df[df.WellName==well]
for param in temp1.Parameter.unique():
fig = plt.figure()
temp2 = temp1[temp1.Parameter==param]
plt.scatter(temp2.SampleDate,temp2.Value)
plt.title('Well {} and Parameter {}'.format(well,param))

Using Bokeh for a dropdown menu which would create different charts

I am trying to create a dropdown interface for my work. My dataset looks like this, it is a random dataset
Now I would like 2 dropdowns say CNN and BBC here. After selecting a channel from dropdown, I would like to select a Topic which would produce a bar chart according to it's value.
I am trying to access just one value initially, but it gives me a blank graph.
from bokeh.plotting import figure
from bokeh.io import output_notebook,show,output_file
p=figure()
import csv
data = [row for row in csv.reader(open('C:/Users/Aishwarya/Documents/books/books_q4/crowd_computing/Bokeh-Python-Visualization-master/interactive/data/data.csv', 'r',encoding="utf8"))]
p.vbar(x=data[1][2], width=0.5, bottom=0,
top=data[1][1], color="firebrick")
#output_notebook()
output_file('1.html')
show(p)
There are probably two issues going on:
The first is that if you are using categorical coordinates on an axis, e.g. "CNN" which it appears you are expecting to use, then you need to etll Bokeh what the categorical range is:
p.figure(x_range=["CNN", ...]) # list all the factors for x_range
If you need to update the axis later you can update the range directly:
p.x_range.factors = [...]
Additionally, as of Bokeh 0.13.0 there is a current open issue that prevents "single" factors from working as coordinates: #6660 Coordinates should accept single categorical values. The upshot is that you will have to put the data in a Bokeh ColumnDataSource explicityl (always an option), or in this case a workaround is also just to pass a single-item list instead:
p.vbar(x=["cnn"], ...)
Here is a complete update of your code, with some fake data put in:
from bokeh.plotting import figure
from bokeh.io import show
p = figure(x_range=["cnn"])
p.vbar(x=["cnn"], width=0.5, bottom=0, top=10, color="firebrick")
show(p)
I would also recommend studying the User's guide section Handling Categorical Data.

Dynamic indexes while zooming in Python Bokeh

I am fairly new to Bokeh and try to achieve the following:
I have a dataset with rows containing dates in the format dd-mm-yyyy.
The dates are counted and then plotted.
When zoomed in I want Bokeh to show the indiviudal dates (that works already).
When zoomed out I want Bokeh only to show the months (or years when zoomed out even further). Right know the index gets pretty messy due to individual dates getting closer and closer the more you zoom out.
Is there a way to tell Bokeh to change what is shown in the index depending on how far you zoomed in or out?
Here is my code:
import pandas as pd
from bokeh.charts import TimeSeries
from bokeh.io import output_file, show, gridplot
transactionssent = dict(pd.melt(df,value_vars=['datesent']).groupby('value').size())
transactionssent2 = pd.DataFrame.from_dict(transactionssent, orient= 'index')
transactionssent2.columns = ['Amount']
transactionssent2.index.rename('Date sent', inplace= True)
ts = TimeSeries(transactionssent2, x='index', y='Amount')
ts.xaxis.axis_label = 'Date sent'
If someone knows please point me in the right direction.
Thanks and best regards,
Stefan
What you've described as what you want already sounds like the standard behavior of the built in datetime axis. So, my guess is that TimeSeries is treating your dates as string/categorical values, which would explain why you are not seeing standard datetime axis scaling.
I should add that bokeh.charts (including TimeSeries) has recently been removed to a separate project and also is known to have problems. I would actually discourage it's use at this point. Fortunately, it's also easy to plot timeseries with the bokeh.plotting API, which is stable, well-tested and documented, and in widespread use.
Here is an example to demonstrate:
import datetime
import numpy as np
from bokeh.io import show, output_file
from bokeh.plotting import figure
# some fake data just for this example, Pandas columns work fine too
start = datetime.datetime(2017, 1, 1)
x = np.array([start + datetime.timedelta(hours=i) for i in range(800)])
y = np.sin(np.linspace(0, 2, len(x))) + 0.05 * np.random.random(len(x))
p = figure(x_axis_type="datetime")
p.line(x, y)
output_file("stocks.html")
show(p)
Whose axis looks like this when first displayed:
But like this when zoomed in:
You can also further customize how the dates are formatter by setting various properties on the p.xaxis[0].formatter. For details about available properties, see the reference guide:
http://docs.bokeh.org/en/latest/docs/reference/models/formatters.html#bokeh.models.formatters.DatetimeTickFormatter

Categories