I want to plot a bar chart on a map created with plotly, similar to the QGIS plot here. Ideally, the bar chart would be stacked and grouped instead of just grouped. So far, I only found examples for pie charts on plotly maps, for instance here.
with plotly mapbox you can add layers
with plotly you can generate images from figures
using above two facts you can add URI encoded images to a mapbox figure
you have not provided any sample geometry or data. Have used a subset geopandas sample geometry plus generated random data for each country (separate graph)
the real key to this solution is layer-coordinates
get centroid of a country
add a buffer around this and get envelope (bounding rectangle)
arrange co-ordinates of envelope to meet requirements stated in link
import geopandas as gpd
import plotly.express as px
import numpy as np
import base64, io
# create an encocded image of graph...
# change to generate graph you want
def b64image(vals=np.random.randint(1, 25, 5)):
fig = px.bar(
pd.DataFrame({"y": vals}).pipe(
lambda d: d.assign(category=d.index.astype(str))
),
y="y",
color="category",
).update_layout(
showlegend=False,
xaxis_visible=False,
yaxis_visible=False,
bargap=0,
margin={"l": 0, "r": 0, "t": 0, "b": 0},
autosize=False,
height=100,
width=100,
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
)
b = io.BytesIO(fig.to_image(format="png"))
b64 = base64.b64encode(b.getvalue())
return "data:image/png;base64," + b64.decode("utf-8"), fig
# get some geometry
world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
# let's just work with a bounded version of europe
eur = world.loc[
lambda d: d["continent"].eq("Europe")
& ~d["iso_a3"].isin(["RUS", "NOR", "FRA", "ISL"])
]
px.choropleth_mapbox(
eur,
geojson=eur.__geo_interface__,
locations="iso_a3",
featureidkey="properties.iso_a3",
color_discrete_sequence=["lightgrey"],
).update_layout(
margin={"l": 0, "r": 0, "t": 0, "b": 0},
showlegend=False,
mapbox_style="carto-positron",
mapbox_center={
"lon": eur.unary_union.centroid.x,
"lat": eur.unary_union.centroid.y,
},
mapbox_zoom=3,
# add a plotly graph per country...
mapbox_layers=[
{
"sourcetype": "image",
# no data provided, use random values for each country
"source": b64image(vals=np.random.randint(1, 25, 5))[0],
# https://plotly.com/python/reference/layout/mapbox/#layout-mapbox-layers-items-layer-coordinates
# a few hops to get 4 cordinate pairs to meet mapbox requirement
"coordinates": [
list(p) for p in r.geometry.centroid.buffer(1.1).envelope.exterior.coords
][0:-1][::-1],
}
for i, r in eur.iterrows()
],
)
output
Related
I'm trying to create a heatmap for countries. I've created a custom geojson, which is working fine, by it's own.
Unfortunately, when I link to the dataframe where the amount are displayed, only part of the heat map are rendered, excluding some areas.
Why is that?
Thanks a lot
data and code available here
You have missed one important parameter: featureidkey This is the join between the geometry and data frame (location)
Have also simplified your aggregate to removed need for reset_index()
full code
import geopandas as gpd
import pandas as pd
import plotly.express as px
gdf = gpd.read_file(
"https://raw.githubusercontent.com/vincenzojrs/test/main/map-2.geojson"
)
sellers = pd.read_csv(
"https://raw.githubusercontent.com/vincenzojrs/test/main/sellers.csv"
)
sellersxcity = sellers.groupby(["id_ac"], as_index=False).agg({"num_ord_sell": "sum"})
fig = px.choropleth_mapbox(
sellersxcity,
geojson=gdf,
featureidkey="properties.ID_1",
locations="id_ac",
color="num_ord_sell",
color_continuous_scale="Viridis",
mapbox_style="carto-positron",
zoom=3,
center={"lat": 40.4999, "lon": -3.673},
labels={"num_ord_sell": "Count for Orders"},
)
fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})
I would like to do something quite similar to the picture with plotly on python. I tried to find a way with subplots and shared_axis but no way to find a correct way. Is it possible to share the x axis of a bar chart with the column titles of a table?
graph bar with shared xaxis
this can be simulated with two traces
first trace is a standard bar chart, with yaxis domain constrained to 80% of the figure
second trace is a bar showing values as text and a fixed height against a second yaxis. yaxis2 is constrained to 10% of the domain
import plotly.express as px
import pandas as pd
import numpy as np
df = pd.DataFrame({"year": range(2011, 2022)}).assign(
pct=lambda d: np.random.uniform(-0.08, 0.08, len(d))
)
px.bar(df, x="year", y="pct").add_traces(
px.bar(df, x="year", y=np.full(len(df), 1), text="pct")
.update_traces(
yaxis="y2",
marker={"line": {"color": "black", "width": 1.5}, "color": "#E5ECF6"},
texttemplate="%{text:,.2%}",
)
.data
).update_layout(
yaxis={"domain": [0.2, 1], "tickformat": ",.2%"},
yaxis2={"domain": [0, 0.1], "visible": False},
xaxis={"title": "", "dtick": 1},
)
I have two separate map plots, one is cultural boundaries in a country (just like the state borders) in the form of custom polygons based on latitude and longitude values, defined in geojson format. I can plot the polygons easily using geopandas:
states = gpd.read_file('myfile.geojson')
states.boundary.plot()
Here is a sample output:
The second is a series of latitudes and longitudes with corresponding values that I need to plot over a map layer, which I can do with plotly express's scatter_mapbox:
fig = px.scatter_mapbox(df_year,
lat='y', lon='x',
color='drought_index',
range_color=(-4, 4),
hover_data={'x': False, 'y': False},
zoom=5, height=800, width=1050,
center={'lat': 32.7089, 'lon': 53.6880},
color_continuous_scale=px.colors.diverging.RdYlGn,
color_continuous_midpoint=0,
)
fig.update_layout(mapbox_style="outdoors", mapbox_accesstoken=mb_token)
Which will look like this:
Is there any way to add these two plots together and have the scatter points and shape boundaries overlap on a single map? Meaning that on top of the mapbox layer, I have the scatter points and the boundaries of the polygons visible.
The problem is that geopandas plot uses matplotlib and returens AxesSubplot:, and I couldn't find any way to add this to the plotly fig. I tried the mpl_to_plotly() from plotly.tools, but it threw an exception on 'Canvas is null'.
I also tried to find a way to plot the geojson shapes with plotly, but all I could find was the choropleth mapbox which requires the shapes to be filled with a color. I tried to use it anyways by decreasing the opacity of the choropleth plot but it either will cover the scatter plot or be barely visible.
Any suggestion on how to approach this is appreciated.
you really described the solution. https://plotly.com/python/mapbox-layers/
have used UK county boundaries as cultural layer
have used UK hospitals to generate a scatter mapbox
"source": json.loads(gdf.geometry.to_json()), is really the solution to add a GEOJSON layer from a geopandas dataframe
import requests
import geopandas as gpd
import pandas as pd
import json, io
import plotly.express as px
# UK admin area boundaries
res = requests.get("https://opendata.arcgis.com/datasets/69dc11c7386943b4ad8893c45648b1e1_0.geojson")
# geopandas dataframe of "cultural layer"
gdf = gpd.GeoDataFrame.from_features(res.json()["features"], crs="CRS84")
# get some public addressess - hospitals. data that can be scattered
dfhos = pd.read_csv(io.StringIO(requests.get("http://media.nhschoices.nhs.uk/data/foi/Hospital.csv").text),
sep="¬",engine="python",)
fig = (
px.scatter_mapbox(
dfhos.head(100),
lat="Latitude",
lon="Longitude",
color="Sector",
hover_data=["OrganisationName", "Postcode"],
)
.update_traces(marker={"size": 10})
.update_layout(
mapbox={
"style": "open-street-map",
"zoom": 5,
"layers": [
{
"source": json.loads(gdf.geometry.to_json()),
"below": "traces",
"type": "line",
"color": "purple",
"line": {"width": 1.5},
}
],
},
margin={"l": 0, "r": 0, "t": 0, "b": 0},
)
)
fig.show()
I am trying to use custom hexa codes for each bar in a plotly chart but I am not able to work this out.
Could someone please help me.
Below is the code I a working with
#Defining Custom Colors
colours = {'Base_Models': '#0C3B5D',
'Standard_scaled_scores': '#3EC1CD',
'Min_Max_scaled_scores': '#EF3A4C',
'Scaling & feature selection_scores': '#FCB94D'}
import plotly.express as px
fig = px.bar(compareModels_aft_Cleansing, x="Base_Models", y=["Base_Models_Scores",
"Standard_scaled_scores", "Min_Max_scaled_scores",
"Scaling & feature selection_scores"],
title="Training Scores", barmode='group', text = 'value',
hover_name="Base_Models",
hover_data={'Base_Models':False}, # remove species from hover data
color = colours)
you have not provided sample data so I have synthesized
your colours map as I understand your dataframe is incorrect. You are plotting Base_Models_Scores as a bar not Base_Models, this is the x-axis
the parameter you require is color_discrete_map to achieve your requirement
import pandas as pd
import numpy as np
# Defining Custom Colors
colours = {
"Base_Models_Scores": "#0C3B5D",
"Standard_scaled_scores": "#3EC1CD",
"Min_Max_scaled_scores": "#EF3A4C",
"Scaling & feature selection_scores": "#FCB94D",
}
# generate sample data...
compareModels_aft_Cleansing = pd.DataFrame(
{
**{"Base_Models": colours.keys()},
**{
c: np.random.randint(1, 4, len(colours.keys()))
for c in colours.keys()
},
}
)
import plotly.express as px
fig = px.bar(
compareModels_aft_Cleansing,
x="Base_Models",
y=[
"Base_Models_Scores",
"Standard_scaled_scores",
"Min_Max_scaled_scores",
"Scaling & feature selection_scores",
],
title="Training Scores",
barmode="group",
text="value",
hover_name="Base_Models",
hover_data={"Base_Models": False}, # remove species from hover data
color_discrete_map=colours,
)
fig
For research data visualisation I'd like to make an animated 3D surface plot in Plotly. The goal is to see the evolution of temperature in a box in function of time. But I don't know how to animate it.
At this moment I only have my plot at a give time.
This is my code:
import plotly
import plotly.graph_objects as go
#import plotly.express as px
import pandas as pd
#import numpy as np
#read CSV
z_data = pd.read_csv('data1.csv')# Read data from a csv
fig = go.Figure(data=[go.Surface(z=z_data.values)])
#projection 2D
fig.update_traces(contours_z=dict(show=True, usecolormap=True,
highlightcolor="tomato", project_z=True),
colorscale='portland')
#fig
fig.update_layout(title='data HEATPILES', autosize=False, width=650, height=500, margin=dict(l=0, r=0, b=0, t=0))
#show
plotly.offline.plot(fig)
data1.csv is only this:
data1.csv
But I have more data of the point's position in function of time and I would want to make an animated plot, so we could clearly see the evolution on time.
Here is the result at a given time
Plot at a given time
I've seen on the plotly documentation that it's possible to make animation with px.scatter and px.line from here, and from there that we can do it with image, so I guess it would be possible with surface plot.
Here is some example of the animation: https://plotly.com/python/#animations
Here is some example of the 3D surface plot: https://plotly.com/python/3d-surface-plots
If you could help me do you I would much appreciate !
Thank you for your help,
Theophile
Here is the full code for you:
import pandas as pd
import plotly.graph_objects as go
z_data = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv').values
print(z_data)
z_data2 = z_data * 1.1
z_data3 = z_data * 1.2
z_data4 = z_data * 0.5
z_data_list = []
z_data_list.append(z_data)
z_data_list.append(z_data2)
z_data_list.append(z_data3)
z_data_list.append(z_data4)
z_data_list.append(z_data)
z_data_list.append(z_data2)
z_data_list.append(z_data3)
z_data_list.append(z_data4)
fig = go.Figure(
data=[go.Surface(z=z_data_list[0])],
layout=go.Layout(updatemenus=[dict(type="buttons", buttons=[dict(label="Play", method="animate", args=[None])])]),
frames=[go.Frame(data=[go.Surface(z=k)], name=str(i)) for i, k in enumerate(z_data_list)]
)
fig.update_traces(contours_z=dict(show=True, usecolormap=True, highlightcolor="tomato", project_z=True), colorscale='portland')
fig.update_layout(title='data HEATPILES', autosize=False, width=650, height=500, margin=dict(l=0, r=0, b=0, t=0))
def frame_args(duration):
return {
"frame": {"duration": duration},
"mode": "immediate",
"fromcurrent": True,
"transition": {"duration": duration, "easing": "linear"},
}
sliders = [
{
"pad": {"b": 10, "t": 60},
"len": 0.9,
"x": 0.1,
"y": 0,
"steps": [
{
"args": [[f.name], frame_args(0)],
"label": str(k),
"method": "animate",
}
for k, f in enumerate(fig.frames)
],
}
]
fig.update_layout(sliders=sliders)
import plotly.io as pio
ii = 1
pio.write_html(fig, file="Live3D_"+str(ii)+".html", auto_open=True)
# plotly.offline.plot(fig)
After a good research I built this code to plot a proper smooth 3D surface plot. Simply put the data_frame into this function. You'll get a proper smoothen surface plot. Incase you face any error, just choose only those features from data_frame which are numerical.
'data_frame = data_frame.select_dtypes(include='number')'
from scipy import interpolate
from mpl_toolkits.mplot3d import axes3d, Axes3D
def surface(data_frame, title=None, title_x=0.5, title_y=0.9):
X, Y = np.mgrid[-10:10:complex(0,data_frame.shape[0]),
-10:10:complex(0,data_frame.shape[1])]
Z = data_frame.values
xnew, ynew = np.mgrid[-1:1:80j, -1:1:80j]
tck = interpolate.bisplrep(X, Y, Z, s=0)
znew = interpolate.bisplev(xnew[:,0], ynew[0,:], tck)
fig = go.Figure(data=[go.Surface(z=znew)])
fig.update_layout(template='plotly_dark',
width=800,
height=800,
title = title,
title_x = title_x,
title_y = title_y
)
return fig