How to include a matplotlib graph for an interactive dashboard? - matplotlib

I want to include a line chart (constructed with matplotlib) in an interactive dashboard. My graph describes the evolution for one year of the frequency of the word "France" in 7 media for Central Africa. The database is called: "df_france_pivot".
What I've seen so far is that first of all I have to transform my plot into an object with the go.figure function. So I tried this code:
`app = dash.Dash()
def update_graph():
plt.style.use('seaborn-darkgrid')
fig, ax = plt.subplots()
ax.set_prop_cycle(color=['304558', 'FE9235', '526683', 'FE574B', 'FFD104', '6BDF9C'])
num=0
for column in df_france_pivot.drop('month_year', axis=1):
num+=1
plt.plot(df_france_pivot['month_year'], df_france_pivot[column], marker='',
linewidth=1, alpha=0.9, label=column)
plt.xticks(rotation=45)
plt.legend(loc=0, prop={'size': 9},bbox_to_anchor=(1.05, 1.0), title='Media in South Africa')
plt.title("Frequency of the word 'France' in the media ", loc='left', fontsize=12, fontweight=0, color='orange')
plt.xlabel("Time")
plt.ylabel("Percentage")
figure = go.Figure(fig)
return figure
app.layout = html.Div(id = 'parent', children = [
html.H1(id = 'H1', children = 'Styling using html components', style = {'textAlign':'center',\
'marginTop':40,'marginBottom':40}),
dcc.Graph(id = 'line_plot', figure = update_graph())
]
)`
When running it I got this response: Output exceeds the size limit. Open the full output data in a text editor. Is it because my linechart is more complex i.e. with 7 lines?
Thank you in advance!

Related

Choropleth us-states map showing incorrect data and color coding

I am trying to show hospitals by type in US states. The dataset I am using is here https://www.kaggle.com/carlosaguayo/usa-hospitals
I am using choropleth and here is my code. I basically have a dropdown with the type of hospital and when select, I am getting the count
#app.callback(Output('figure-1', 'figure'),
[Input('options-drop', 'value')])
def make_figure(varname):
mygraphtitle = f'Hospitals of {varname}'
mycolorscale = 'Blues'
mycolorbartitle = "Count"
data=go.Choropleth(
locations=df['STATE'],
locationmode = 'USA-states',
z = df[df["TYPE"] == varname]["STATE"].value_counts(),
colorscale = mycolorscale,
colorbar_title = mycolorbartitle,
)
fig = go.Figure(data)
fig.update_layout(
title_text = mygraphtitle,
geo_scope='usa',
width=1200,
height=800
)
return fig
I have 3 issues with the grpah
Data is not being shown for all states
Data shown for few states is incorrect
Color coding is incorrect even for those states with incorrect data. state with higher hospital count is shown with lighter blue whereas with lower count is shown with darker blue
You can see below from pandas, I can tell Texas has only 65 critical access hospitals but the US map shows count as 78 and even if it was 78, the color of Texas is light blue compared with other state with lower hospitals. Where am I going wrong?
I have not verified this in the Dash environment, but I believe the operation will be the same. The cause is that you are specifying a state column for the entire data frame you are setting up. The easiest response is to target the filtered data frame.
import plotly.graph_objects as go
import pandas as pd
df = pd.read_csv('./data/Hospitals.csv')
varname = 'CRITICAL ACCESS'
filtered_df = df[df["TYPE"] == varname]["STATE"].value_counts().to_frame('value')
#print(filtered_df)
fig = go.Figure(data=go.Choropleth(
locations=filtered_df.index,
z = filtered_df['value'],
locationmode = 'USA-states',
colorscale = 'Blues',
colorbar_title = "Count",
))
fig.update_layout(
title_text = f'Hospitals of {varname}',
geo_scope='usa',
width=1200,
height=800
)
fig.show()

Is there a way to have multiple rectangles displayed at the same time using visual.Rect in PsychoPy?

I'm trying to create stimuli that consist of 100 small lines in the centre of the screen, with orientations sampled from a Gaussian distribution (please see the image link below):
Orientation stimuli
I've managed to achieve something that almost fits the bill, but this code only works in isolation:
from psychopy import visual, core, event
import numpy as np
from numpy.random import random
import random
Lines = visual.Rect(
win=win, name='Lines',
width=(0.015, 0.0025)[0], height=(0.015, 0.0025)[1],
lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
fillColor=[1,1,1], fillColorSpace='rgb',
opacity=1, depth=-2.0, interpolate=True)
lines_hpos = np.random.uniform(-0.49,0.49,100)
mu = 315
sigma = 15
for i in range(100):
Lines.pos = [lines_hpos[i],np.random.uniform(-0.49,0.49)]
Lines.ori = random.gauss(mu, sigma)
I've tried to manipulate this code so that I can integrate it into the full experiment I'm designing in PsychoPy's experiment builder. I run the below code in the experiment builder's coding window calling 'gdist' and 'loc' as values for the 'Orientation' and 'Position' of the rectangles, respectively:
import random
gdist =[]
loc = []
lines_hpos = np.random.uniform(-0.49,0.49,100)
mu = 90
sigma = 20
for i in range(100):
rloc = [lines_hpos[i],np.random.uniform(-0.49,0.49)]
loc.append(rloc)
gauss = random.gauss(mu, sigma)
gdist.append(gauss)
When I attempt to run the experiment, I get an error return and the experiment fails to start:
File "C:\Users\r02mj20\AppData\Local\PsychoPy3\lib\site-packages\psychopy\visual\image.py", line 238, in __del__
File "C:\Users\r02mj20\AppData\Local\PsychoPy3\lib\site-packages\pyglet\gl\lib.py", line 97, in errcheck
ImportError: sys.meta_path is None, Python is likely shutting down
I'm assuming this has something to do with pyglet not liking the idea of there being 100 rectangles all at once (side note: the script works fine if range(1)). If anyone has any suggestions for how I might fix or work around this problem, I'd be eternally grateful.
i don't see any problem with this idea, except you better use visual.Line instead of Rect, and your units of measure are not described; the key to preserving video memory is BufferImageStim, btw
from psychopy import visual, core, event, monitors
from psychopy.iohub.client import launchHubServer
import random
import numpy as np
MU = 315; SIGMA = 15
num_lines = 100
io = launchHubServer(iohub_config_name='iohub_config.yaml')
display = io.devices.display
mon = monitors.Monitor(name = display.getPsychopyMonitorName())
win = visual.Window([640, 480], units='pix', viewScale = 1.0,
monitor = mon, winType='pyglet',
fullScr = False, waitBlanking = True, useFBO = True, useLights = False,
allowStencil=False, allowGui = True,
screen = display.getIndex(), colorSpace = 'rgb255', color = [128,128,128],
name = 'my_win01')
rects = []
lines_hpos = np.random.uniform(-0.49, 0.49, num_lines)
for i in range(num_lines):
line_rect = visual.Rect(win=win, size=(0.001, 1.0), units='norm',
pos=(0,0), lineWidth=1, lineColor=[1,1,1], fillColor=[1,1,1], opacity=1, depth=-2.0,
name='lines_rect', interpolate=True, autoLog=False, autoDraw=False)
line_rect.pos = [lines_hpos[i], np.random.uniform(-0.49,0.49)]
line_rect.ori = random.gauss(MU, SIGMA)
rects.append(line_rect)
rect_buffer = visual.BufferImageStim(win, buffer='back', stim=rects, sqPower2=False, interpolate=False, name='rect-buffer', autoLog=True)
rect_buffer.draw()
win.flip()
event.waitKeys()

How to create textbox on figure using first row in geodataframe?

I am looking to plot a textbox on a figure displaying the 5-Day NHC forecast cone for a tropical cyclone, in this case Hurricane Dorian. I have the four shapefiles (track line, cone, points, and watches/warnings). On the figure I want to display the following from the first row of points_gdf (yellow circles in the image; the two commented out lines near the bottom of the code is what I tried initially):
Latest Tracking Information: (regular string; below are variables from points_gdf)
LAT LON
MAXWIND
GUST
MSLP
TCSPD
track_line_gdf = geopandas.read_file('nhc/al052019_5day_037/al052019-037_5day_lin.shp')
cone_gdf = geopandas.read_file('nhc/al052019_5day_037/al052019-037_5day_pgn.shp')
points_gdf = geopandas.read_file('nhc/al052019_5day_037/al052019-037_5day_pts.shp')
ww_gdf = geopandas.read_file('nhc/al052019_5day_037/al052019-037_ww_wwlin.shp')
fig = plt.figure(figsize=(14,12))
fig.set_facecolor('white')
ax = plt.subplot(1,1,1, projection=map_crs)
ax.set_extent([-88,-70,25,50])
ax.add_geometries(cone_gdf['geometry'], crs=data_crs, facecolor='white',
edgecolor='black', linewidth=0.25, alpha=0.4)
ax.add_geometries(track_line_gdf['geometry'], crs=data_crs, facecolor='none',
edgecolor='black', linewidth=2)
sc = ax.scatter(points_gdf['LON'], points_gdf['LAT'], transform=data_crs,
zorder=10, c=points_gdf['MAXWIND'], cmap='jet')
ww_colors = {'Tropical Storm Watch': 'gold',
'Hurricane Watch': 'pink',
'Tropical Storm Warning': 'tab:blue',
'Hurricane Warning': 'tab:red'}
for ww_type in ww_colors.keys():
ww_subset = ww_gdf[ww_gdf['TCWW']==ww_type]
ax.add_geometries(ww_subset['geometry'], facecolor='none',
edgecolor=ww_colors[ww_type], crs=data_crs,
linewidth=5)
markers = [plt.Line2D([0,0],[0,0],color=color, marker='o', linestyle='') for color in ww_colors.values()]
Name = ww_gdf['STORMNAME'][0]
Storm = ww_gdf['STORMTYPE'][0]
AdvDate = ww_gdf['ADVDATE'][0]
AdvNum = ww_gdf['ADVISNUM'][0]
props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
plt.colorbar(sc, label='Wind Speed (mph)')
plt.title(Storm + ' ' + Name + ' - ' + AdvDate + ' Advisory', fontsize=14, fontweight='bold')
plt.legend(markers, ww_colors.keys())
plt.text(0.05, 0.95, 'Testing', transform=ax.transAxes, va='top', bbox=props)
It would help to know either what error you're running into, or what exactly isn't behaving how you want. I can slightly tweak your code to make this:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(14,12))
fig.set_facecolor('white')
ax = plt.subplot(1,1,1, projection=ccrs.LambertConformal())
plt.title('Storm Advisory', fontsize=14, fontweight='bold')
points_gds = pd.DataFrame(dict(GUST=[165.0], LAT=[26.8],
LON=[-78.3], MSLP=[930.2]))
storminfo = f'''Max Wind Gusts: {points_gds.iloc[0]['GUST']:.0f} mph
Current Latitude: {points_gds.iloc[0]['LAT']:.1f}
Current Longitude: {points_gds.iloc[0]['LON']:.1f}
Central Pressure: {points_gds.iloc[0]['MSLP']:.2f} mb'''
props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
plt.text(0.05, 0.95, 'Testing', transform=ax.transAxes, va='top', bbox=props)
ax.coastlines()
ax.set_extent([-88,-70,25,50])
which produces this image:
To make that work I needed to change round (which is a Python built-in function) to the string 'round'. The text is formatted using f-strings ("formatted string literals"), and enclosed as a triple-quoted string to avoid needing to manually put in the newline ('\n') characters. Python's docs can tell you more about how to control the formatting of individual items.

How to use hover events in mpl_connect in matplotlib

I'm working on line plotting a metric for a course module as well as each of its questions within a Jupyter Notebook using %matplotlib notebook. That part is no problem. A module has typically 20-35 questions, so it results in a lot of lines on a chart. Therefore, I am plotting the metric for each question in a low alpha and I want to change the alpha and display the question name when I hover over the line, then reverse those when no longer hovering over the line.
The thing is, I've tried every test version of interactivity from the matplotlib documentation on event handling, as well as those in this question. It seems like the mpl_connect event is never firing, whether I use click or hover.
Here's a test version with a reduced dataset using the solution to the question linked above. Am I missing something necessary to get events to fire?
def update_annot(ind):
x,y = line.get_data()
annot.xy = (x[ind["ind"][0]], y[ind["ind"][0]])
text = "{}, {}".format(" ".join(list(map(str,ind["ind"]))),
" ".join([names[n] for n in ind["ind"]]))
annot.set_text(text)
annot.get_bbox_patch().set_alpha(0.4)
def hover(event):
vis = annot.get_visible()
if event.inaxes == ax:
cont, ind = line.contains(event)
if cont:
update_annot(ind)
annot.set_visible(True)
fig.canvas.draw_idle()
else:
if vis:
annot.set_visible(False)
fig.canvas.draw_idle()
module = 'bd2bc472-ee0d-466f-8557-788cc6de3018'
module_metrics[module] = {
'q_count': 31,
'sequence_pks': [0.5274546300604932,0.5262044653349001,0.5360993905297703,0.5292329279700655,0.5268691588785047,0.5319099014547161,0.5305164319248826,0.5268235294117647,0.573648805381582,0.5647933116581514,0.5669839795681448,0.5646591970121382,0.5663157894736842,0.5646976090014064,0.5659005628517824,0.5693634879925391,0.5728268468888371,0.5668834184858337,0.5687237026647967,0.5795640965549567,0.5877684407096172,0.585690904839841,0.5766899766899767,0.5971341320178529,0.6059972105997211,0.6055516678329834,0.6209865053513262,0.6203121360354065,0.6153666510976179,0.6236909471724459,0.6387654898293196],
'q_pks': {
'0da04f02-4aad-4ac8-91a5-214862b5c0d0': [0.6686046511627907,0.6282051282051282,0.76,0.6746987951807228,0.7092198581560284,0.71875,0.6585365853658537,0.7070063694267515,0.7171052631578947,0.7346938775510204,0.7737226277372263,0.7380952380952381,0.6774193548387096,0.7142857142857143,0.7,0.6962962962962963,0.723404255319149,0.6737588652482269,0.7232704402515723,0.7142857142857143,0.7164179104477612,0.7317073170731707,0.6333333333333333,0.75,0.7217391304347827,0.7017543859649122,0.7333333333333333,0.7641509433962265,0.6869565217391305,0.75,0.794392523364486],
'10bd29aa-3a26-49e6-bc2c-50fd503d7ab5': [0.64375,0.6014492753623188,0.5968992248062015,0.5059523809523809,0.5637583892617449,0.5389221556886228,0.5576923076923077,0.51875,0.4931506849315068,0.5579710144927537,0.577922077922078,0.5467625899280576,0.5362318840579711,0.6095890410958904,0.5793103448275863,0.5159235668789809,0.6196319018404908,0.6143790849673203,0.5035971223021583,0.5897435897435898,0.5857142857142857,0.5851851851851851,0.6164383561643836,0.6054421768707483,0.5714285714285714,0.627906976744186,0.5826771653543307,0.6504065040650406,0.5864661654135338,0.6333333333333333,0.6851851851851852]
}}
suptitle_size = 24
title_size = 18
tick_size = 12
axis_label_size = 15
legend_size = 14
fig, ax = plt.subplots(figsize=(15,8))
fig.suptitle('PK by Sequence Order', fontsize=suptitle_size)
module_name = 'Test'
q_count = module_metrics[module]['q_count']
y_ticks = [0.0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]
x_ticks = np.array([x for x in range(0,q_count)])
x_labels = x_ticks + 1
# Plot it
ax.set_title(module_name, fontsize=title_size)
ax.set_xticks(x_ticks)
ax.set_yticks(y_ticks)
ax.set_xticklabels(x_labels, fontsize=tick_size)
ax.set_yticklabels(y_ticks, fontsize=tick_size)
ax.set_xlabel('Sequence', fontsize=axis_label_size)
ax.set_xlim(-0.5,q_count-0.5)
ax.set_ylim(0,1)
ax.grid(which='major',axis='y')
# Output module PK by sequence
ax.plot(module_metrics[module]['sequence_pks'])
# Output PK by sequence for each question
for qid in module_metrics[module]['q_pks']:
ax.plot(module_metrics[module]['q_pks'][qid], alpha=0.15, label=qid)
annot = ax.annotate("", xy=(0,0), xytext=(-20,20),textcoords="offset points", bbox=dict(boxstyle="round", fc="w"), arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
mpl_id = fig.canvas.mpl_connect('motion_notify_event', hover)
Since there are dozens of modules, I created an ipywidgets dropdown to select the module, which then runs a function to output the chart. Nonetheless, whether running it hardcoded as here or from within the function, mpl_connect never seems to fire.
Here's what this one looks like when run

Issue when trying to plot geom_tile using ggplotly

I would like to plot a ggplot2 image using ggplotly
What I am trying to do is to initially plot rectangles of grey fill without any aesthetic mapping, and then in a second step to plot tiles and change colors based on aesthetics. My code is working when I use ggplot but crashes when I try to use ggplotly to transform my graph into interactive
Here is a sample code
library(ggplot2)
library(data.table)
library(plotly)
library(dplyr)
x = rep(c("1", "2", "3"), 3)
y = rep(c("K", "B","A"), each=3)
z = sample(c(NA,"A","L"), 9,replace = TRUE)
df <- data.table(x,y,z)
p<-ggplot(df)+
geom_tile(aes(x=x,y=y),width=0.9,height=0.9,fill="grey")
p<-p+geom_tile(data=filter(df,z=="A"),aes(x=x,y=y,fill=z),width=0.9,height=0.9)
p
But when I type this
ggplotly(p)
I get the following error
Error in [.data.frame(g, , c("fill_plotlyDomain", "fill")) :
undefined columns selected
The versions I use are
> packageVersion("plotly")
1 ‘4.7.1
packageVersion("ggplot2")
1 ‘2.2.1.9000’
##########Edited example for Arthur
p<-ggplot(df)+
geom_tile(aes(x=x,y=y,fill="G"),width=0.9,height=0.9)
p<- p+geom_tile(data=filter(df,z=="A"),aes(x=x,y=y,fill=z),width=0.9,height=0.9)
p<-p+ scale_fill_manual(
guide = guide_legend(title = "test",
override.aes = list(
fill =c("red","white") )
),
values = c("red","grey"),
labels=c("A",""))
p
This works
but ggplotly(p) adds the grey bar labeled G in the legend
The output of the ggplotly function is a list with the plotly class. It gets printed as Plotly graph but you can still work with it as a list. Moreover, the documentation indicates that modifying the list makes it possible to clear all or part of the legend. One only has to understand how the data is structured.
p<-ggplot(df)+
geom_tile(aes(x=x,y=y,fill=z),width=0.9,height=0.9)+
scale_fill_manual(values = c(L='grey', A='red'), na.value='grey')
p2 <- ggplotly(p)
str(p2)
The global legend is here in p2$x$layout$showlegend and setting this to false displays no legend at all.
The group-specific legend appears at each of the 9 p2$x$data elements each time in an other showlegend attribute. Only 3 of them are set to TRUE, corresponding to the 3 keys in the legend. The following loop thus clears all the undesired labels:
for(i in seq_along(p2$x$data)){
if(p2$x$data[[i]]$legendgroup!='A'){
p2$x$data[[i]]$showlegend <- FALSE
}
}
Voilà!
This works here:
ggplot(df)+
geom_tile(aes(x=x,y=y,fill=z),width=0.9,height=0.9)+
scale_fill_manual(values = c(L='grey', A='red'), na.value='grey')
ggplotly(p)
I guess your problem comes from the use of 2 different data sources, df and filter(df,z=="A"), with columns with the same name.
[Note this is not an Answer Yet]
(Putting for reference, as it is beyond the limits for comments.)
The problem is rather complicated.
I just finished debugging the code of plotly. It seems like it's occurring here.
I have opened an issue in GitHub
Here is the minimal code for the reproduction of the problem.
library(ggplot2)
set.seed(1503)
df <- data.frame(x = rep(1:3, 3),
y = rep(1:3, 3),
z = sample(c("A","B"), 9,replace = TRUE),
stringsAsFactors = F)
p1 <- ggplot(df)+
geom_tile(aes(x=x,y=y, fill="grey"), color = "black")
p2 <- ggplot(df)+
geom_tile(aes(x=x,y=y),fill="grey", color = "black")
class(plotly::ggplotly(p1))
#> [1] "plotly" "htmlwidget"
class(plotly::ggplotly(p2))
#> Error in `[.data.frame`(g, , c("fill_plotlyDomain", "fill")): undefined columns selected