Manually defined legend in Plotly on Python - legend

I have some data that is broken down by day. For each day, I have a datapoint at the start and end of the day, each with a value between 0 and 100. I need to display this data as a grouped bar plot with the days on the x axis, values on the y axis and the bars colors are determined by their values. For each day, the left bar needs to have the corresponding start value, and the right bar displays the day's end value. The legend however needs to display information on the color rather than the trace
The plot basically needs to look like this but the legend needs to display "green", "amber", "red" instead of "start", "end".
I need the plot to look like this but with a legend describing the colors rather than the traces
Here is some code to reproduce the plot:
x = ["day"+str(i) for i in range(1,8)]
starts = [10, 50, 70, 75, 20, 50, 90]
ends = [95, 5, 80, 20, 50, 10, 75]
starts_colors = ['green', 'orange', 'red', 'red', 'green', 'orange', 'red']
ends_colors = ['red', 'green', 'red', 'green', 'orange', 'green', 'red']
And here is the code I have for the plot above.
layout = go.Layout(showlegend=True)
fig = go.Figure(layout=layout)
fig.add_trace(go.Bar(x=x, y=starts, name = 'start', marker=dict(color=starts_colors)))
fig.add_trace(go.Bar(x=x, y=ends, name = 'end', marker=dict(color=ends_colors)))
fig.show()
If I rearrange the data into 3 traces (one for each color) with the corresponding values in starts and ends, I end up with gaps between the bars. For example "day1" would have a gap in the middle because there is no orange bar for "day1".
This seems like a simple problem but I'm at a loss as to how to get it to work the way I'm supposed to.

this creates exactly the graph you requested
start by putting your sample data into a dataframe to open up Plotly Express
start by updating traces to use colors columns
adding legend is done. Really is not a functional legend as it cannot be used for filtering the figure, will just show unique colors used in figure. This is achieved by adding additional small traces
import plotly.graph_objects as go
import plotly.express as px
import pandas as pd
import numpy as np
df = pd.DataFrame(
{
"day": ["day" + str(i) for i in range(1, 8)],
"starts": [10, 50, 70, 75, 20, 50, 90],
"ends": [95, 5, 80, 20, 50, 10, 75],
"starts_colors": ["green", "orange", "red", "red", "green", "orange", "red"],
"ends_colors": ["red", "green", "red", "green", "orange", "green", "red"],
}
)
# build figure, hover_data / customdata is used to hold colors
fig = px.bar(
df,
x="day",
y=["starts", "ends"],
barmode="group",
hover_data={"starts_colors":False, "ends_colors":False},
)
# update colors of bars
fig.plotly_update(
data=[
t.update(marker_color=[c[i] for c in t.customdata])
for i, t in enumerate(fig.data)
]
)
# just for display purpose, create traces so that legend contains colors. does not connect with
# bars
fig.update_traces(showlegend=False).add_traces(
[
go.Bar(name=c, x=[fig.data[0].x[0]], marker_color=c, showlegend=True)
for c in np.unique(df.loc[:,["starts_colors","ends_colors"]].values.ravel())
]
)

Related

Setting independent colorbar scale to y-values of plot using matplotlib and proplot

I have a series of histograms that I plot over the top of each other using a for loop:
import matplotlib as plt
import proplot as pplt
cmap = colormap
fig = pplt.figure(figsize=(12, 10), dpi=300)
jj = [ 4, 3, 2, 1, 0]
for j in jj:
plt.fill_between(p[:,j], s[:, j], y2=0, alpha = 0.6, color = colormap[:,4-j], edgecolor=[0,0,0], linewidth=1.5)
The colormap in question is a manually specified list of RGB triplets (from Fabio Crameri's 'lajolla' map):
0.64566 0.823453 0.895061 0.924676 0.957142
0.277907 0.386042 0.526882 0.657688 0.803006
0.259453 0.301045 0.317257 0.331596 0.408285
Each color corresponds to data recorded under different conditions. I want the colorbar to have manually specified ticks corresponding to this variable (e.g. c = 30, 35, 40, 45, 50), but I can't seem to configure the colormap to not just pull the indices of the cmap matrix (0, 1, 2, 3, 4) as the values of the mapped variable. Trying to set the ticks outside of this range just result in them not being shown.
cbar = fig.colorbar(np.transpose(cmap))
cbar.set_ticks([30, 35, 40, 45, 50])
cbar.set_ticklabels([30, 35, 40, 45, 50])
Any idea how I can resolve this?
Tried shifting indices of colormap but this doesn't seem to work.
Trying to get the colorbar with ticks corresponding to the '30, 35, 40, 45, 50' values quoted above.

Place Colorbar to One Side of GeoAxes Plot

I am generating a irregular gridded plot with a globe projection and am utilizing both xarray and CartoPy to achieve this. The following minimal code produces the first image below, note that I am leaving out calling specific packages and specifically defined cmap/norm options, as they remain outside the bounds of my question:
file = '/path/to/data/griddeddata.tif'
da = rxr.open_rasterio(file)
da = ((da * 1.8) + 32)
ny, nx = len(da['y']), len(da['x'])
x, y = np.meshgrid(da['x'], da['y'])
fig = plt.figure(figsize=(14,8))
ax = plt.subplot(projection=crs.LambertConformal())
ax.set_extent([-75.500000, -72.000000, 40.500000, 43.000000], crs=crs.LambertConformal())
im = ax.pcolormesh(x, y, da.variable.data[0], cmap=cmap, norm=norm)
plt.gcf().set_size_inches((14, 8))
plt.gca().set_position([0, 0, 1, 1])
When I add the following code plt.colorbar(im, ax=ax, pad=0.01, ticks=[-50, -40, -30, -20, -10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120], aspect=40), I get a colorbar that appears inside the map plot itself, whereas I would like this colorbar to be oriented vertically to the right.
I suspect that this has to do with the sharing of a georeferenced axis (map plot) and an unreferenced colorbar axis, though I am unsure how to correct the issue. What additional steps are recommended to take in order to achieve the desired result? Thanks!
I would suggest you create additonal axis besides the plot for the colorbar.
The following code can be adjusted to your need.
Define the position
cbar_pos = [0.90, 0.30, 0.03, 0.45] #axis for colorbar left, bottom, width, height
Create the axis
cbar_ax = fig.add_axes(cbar_pos)
cbar_ax.get_xaxis().set_visible(False)
cbar_ax.yaxis.set_ticks_position('right')
cbar_ax.set_yticklabels([])
cbar_ax.tick_params(size=0)`
Pass the cbar_ax into your colorbar function
plt.colorbar(im, cax=cbar_ax, pad=0.01, ticks=[-50, -40, -30, -20, -10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120], aspect=40)

Matplotlib gridspec trouble specifying subplots layout

I'm trying to use plt.GridSpec() to set up two subplots such that the left one takes up about 67% of the space and the right one takes up 33%.
I looked at the documentation, but I just can't seem to figure out how to set up the indexing--probably due a lack of experience with numpy slicing.
Repeatable Example
%matplotlib inline
import matplotlib.pyplot as plt
import pandas as pd
## Dummy Data
x = [0, 0.03, 0.075, 0.108, 0.16, 0.26, 0.37, 0.49, 0.76, 1.05, 1.64,
0.015, 0.04, 0.085, 0.11, 0.165, 0.29, 0.37, 0.6, 0.78, 1.1]
y = [16.13, 0.62, 2.15, 41.083, 59.97, 13.30, 7.36, 6.80, 4.97, 3.53, 11.77,
30.21, 64.47, 57.64, 56.83, 46.69, 4.22, 30.35, 35.12, 5.22, 25.32]
label = ['blue', 'blue', 'blue', 'blue', 'blue', 'blue', 'blue', 'blue', 'blue', 'blue', 'blue',
'red', 'red', 'red', 'red', 'red', 'red', 'red', 'red', 'red', 'red', 'red']
df = pd.DataFrame(
list(zip(x, y, label)),
columns =['x', 'y', 'label']
)
## Plotting
fig = plt.figure(figsize=([10,8]))
grid = plt.GridSpec(1, 3, wspace=0.4, hspace=0.3)
ax1 = plt.subplot(grid[0, :1])
ax2 = plt.subplot(grid[0, 2], sharey = ax1)
ax1.scatter(x=df.y, y=df.x, color=df.label)
df_red = df[df['label'] == "red"]
df_blue = df[df['label'] == "blue"]
myhist = ax2.hist([df_blue.x, df_red.x],
density=False,
edgecolor='black',
color=['blue', 'red'],
cumulative=False,
bins='auto',
orientation='horizontal',
stacked=True,
label=['Blue', 'Red'])
ax1.set_xlabel('length')
ax1.set_ylabel('value')
ax2.set_xlabel('frequency')
ax2.set_ylabel('value')
Current Result
Desired Result
Same plot, just with the left:right ratio at 67% : 33% (so left plot is wider than right plot).
Here's the small modification that you need to make:
# one position less than 3rd column
ax1 = plt.subplot(grid[0, :-1])

matplotlib asymmetric errorbar showing wrong information

I am trying to plot a grouped barplot with asymmetrical errobars. When the error bars a symmetrical, it's producing the correct chart. However, for the asymmetric version, the length of the error bar is wrong.
Here is a minimally reproducible code:
# test with code from documentation
men_means, men_std = (20, 35, 30, 35, 27), (2, 3, 4, 1, 2)
women_means, women_std = (25, 32, 34, 20, 25), (3, 5, 2, 3, 3)
# dummy dataframe similar to what I will be using
avg = [20, 35, 30, 35, 27]
men_std_l = [19,33,28,34,25]
men_std_u = [22,37,31,39,29]
df = pd.DataFrame({'avg' :avg, 'low':men_std_l, 'high':men_std_u})
ind = np.arange(df.shape[0]) # the x locations for the groups
width = 0.35 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(ind - width/2, df['avg'], width, yerr=[df['low'].values,df['high'].values], label='Men')
rects2 = ax.bar(ind + width/2, women_means, width, yerr=women_std,label='Women')
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('Scores')
ax.set_title('error bar is wrong for asymmetrical, correct otherwise')
ax.legend()
fig.tight_layout()
plt.show()
I have tried the solutions from Asymmetrical errorbar with pandas (getting ValueError: In safezip, len(args[0])=5 but len(args1)=1) and plotting asymmetric errorbars using matplotlib (getting TypeError: Cannot cast array data from dtype('< U1') to dtype('float64') according to the rule 'safe')
Any help is much appreciated.
Answering my own question as I could not understand from the documentation what those lower and upper bounds of errors were. In the hindsight, it should have been clearer if I were not so used to with ggplot in r.
The matplotlib version of asymmetrical errorbar requires the the values to add and subtract from the height of the bars. It does not want the user to provide the upper and lower values, rather the numbers that should be added and subtracted. Therefore, I needed the following:
xel = df['avg'].values - df['low'].values
xeh = df['high'].values - df['avg'].values

seaborn or matplotlib line chart, line color depending on variable

I have a pandas dataframe with three columns, Date(timestamp), Color('red' or 'blue') and Value(int).
I am currently getting a line chart from it with the following code:
import matplotlib.pyplot as plt
import pandas as pd
Dates=['01/01/2014','02/01/2014','03/01/2014','04/01/2014','05/01/2014','06/01/2014','07/01/2014']
Values=[3,4,6,5,4,5,4]
Colors=['red','red','blue','blue','blue','red','red']
df=pd.DataFrame({'Dates':Dates,'Values':Values,'Colors':Colors})
df['Dates']=pd.to_datetime(df['Dates'],dayfirst=True)
grouped = df.groupby('Colors')
fig, ax = plt.subplots()
for key, group in grouped:
group.plot(ax=ax, x="Dates", y="Values", label=key, color=key)
plt.show()
I'd like the line color to depend on the 'color' columns. How can I achieve that?
I have seen here a similar question for scatterplots, but it doesn't seem I can apply the same solution to a time series line chart.
My output is currently this:
I am trying to achieve something like this (one line only, but several colors)
As I said you could find the answer from the link I attached in the comment:
Dates = ['01/01/2014', '02/01/2014', '03/01/2014', '03/01/2014', '04/01/2014', '05/01/2014']
Values = [3, 4, 6, 6, 5, 4]
Colors = ['red', 'red', 'red', 'blue', 'blue', 'blue']
df = pd.DataFrame({'Dates': Dates, 'Values': Values, 'Colors': Colors})
df['Dates'] = pd.to_datetime(df['Dates'], dayfirst=True)
grouped = df.groupby('Colors')
fig, ax = plt.subplots(1)
for key, group in grouped:
group.plot(ax=ax, x="Dates", y="Values", label=key, color=key)
When color changing you need to add extra point to make line continuous