Related
I am trying to plot US census data using plotly. I am able to create a plotly map but its not working for plotly graph object. Below is what I tried:
import numpy as np
import pandas as pd
import censusdata as cd
import geopandas as gpd
import plotly.express as px
from plotly import graph_objs as go
#download census data using censusdata library
df = cd.download('acs5', 2020, cd.censusgeo([('state', '36'), ('county', '*'), ('tract', '*')]), ['B01003_001E'])
df.columns = ['TOTAL_POPULATION']
# fix index
new_indices = []
for index in df.index.tolist():
new_index = index.geo[0][1] + index.geo[1][1] + index.geo[2][1]
new_indices.append(new_index)
df['GEOID'] = new_indices
df = df.reset_index(drop=True)
df.head()
# merge with shape file of NY state
path = "shape_files/unzipped/tl_2020_36_tract.shp"
plot = gpd.read_file(path)
plot.plot()
df = pd.merge(df, plot, on='GEOID', how='inner')
# filter data for NY county and plot it using plotly
ny = df[df['COUNTYFP'].isin(['061'])]
geo_df = gpd.GeoDataFrame(ny).set_index("GEOID")
fig = px.choropleth_mapbox(geo_df,
geojson=geo_df.geometry,
locations=geo_df.index,
color="TOTAL_POPULATION", color_continuous_scale=px.colors.sequential.Greens,
center={"lat": 40.8, "lon": -73.97},
mapbox_style="open-street-map",
zoom=11)
fig.update_layout(
autosize=False,
width=1000,
height=1000,
margin={"r":0,"t":0,"l":0,"b":0}
)
fig.show()
The above code works fine and produces below plot:
But when I try to plot it using plotly graph object:
fig = go.Figure(go.Choroplethmapbox(geojson=geo_df.geometry, locations=geo_df.index, z=geo_df.TOTAL_POPULATION,
colorscale="Viridis", zmin=0, zmax=12,
marker_opacity=0.5, marker_line_width=0))
fig.update_layout(mapbox_style="carto-positron",
mapbox_zoom=3, mapbox_center = {"lat": 37.0902, "lon": -95.7129})
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
fig.show()
It throws this error:
TypeError: Object of type Polygon is not JSON serializable
How can I fix it and create a plotly graph object?
P.S, you can download shape files from here
The geodata needed by the graph object must be in geojson format, so the geodata frame interface is used to create a dictionary format. I have created the same content in my graph object code as the map you created in Express.
fig = go.Figure(go.Choroplethmapbox(
geojson=geo_df.__geo_interface__,
locations=geo_df.index,
z=geo_df.TOTAL_POPULATION,
colorscale="Greens",
zmin=0,
zmax=10000,
marker_opacity=0.8,
marker_line_width=1
)
)
fig.update_layout(autosize=False,
width=1000,
height=1000,
mapbox_style="open-street-map",
mapbox_zoom=11,
mapbox_center = {"lat": 40.8, "lon": -73.97})
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
fig.show()
I'm trying to create a weather contour for the United States from an existing data frame and add it to a Dash Mapbox map, but the json file I am creating "fills in" areas where data does not exist in an attempt to fill out the entire array. The unwanted data can be seen shaded in the image below.
I'd like to remove data from the weather json file where the lat-longs from the weather json file and the states json file do not intersect.
Better yet would be a solution where weather data was never created at all for areas outside of the states_20m.geojson.
The pertinent data files can be found at this GitHub Link. They are the weather dataframe and the states_20m.geojson.
Below is my code.
import pandas as pd
from datetime import datetime
import plotly.express as px
import plotly.graph_objects as go
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.interpolate import griddata,RectSphereBivariateSpline,Rbf
import geojsoncontour
import json
import branca
import scipy as sp
import scipy.ndimage
from geojson import Feature, Polygon, dump
import geopandas as gpd
##### Load in the main DataFrame and define vars#####
path = r'date_data.csv'
df = pd.read_csv(path, index_col=[0])
col = 'Day_Temp'
temp_levels = [-20,0,10,20,32]
levels = temp_levels
unit = 'deg F'
colors = ['#f0ffff','#add8e6','#7bc8f6','#069af6','#0343df'
##### Create the weather contour #####
data = []
df_copy = df.copy()
##### Create the GEOJSON Layer #####
vmin = 0
vmax = 1
cm = branca.colormap.LinearColormap(colors, vmin=vmin, vmax=vmax).to_step(len(levels))
x_orig = (df_copy.long.values.tolist())
y_orig = (df_copy.lat.values.tolist())
z_orig = np.asarray(df_copy[col].values.tolist())
x_arr = np.linspace(np.min(x_orig), np.max(x_orig), 5000)
y_arr = np.linspace(np.min(y_orig), np.max(y_orig), 5000)
x_mesh, y_mesh = np.meshgrid(x_arr, y_arr)
xscale = df_copy.long.max() - df_copy.long.min()
yscale = df_copy.lat.max() - df_copy.lat.min()
scale = np.array([xscale, yscale])
z_mesh = griddata((x_orig, y_orig), z_orig, (x_mesh, y_mesh), method='linear')
sigma = [5, 5]
z_mesh = sp.ndimage.filters.gaussian_filter(z_mesh, sigma, mode='nearest')
# Create the contour
contourf = plt.contourf(x_mesh, y_mesh, z_mesh, levels, alpha=0.9, colors=colors,
linestyles='none', vmin=vmin, vmax=vmax)
# Convert matplotlib contourf to geojson
geojson = geojsoncontour.contourf_to_geojson(
contourf=contourf,
min_angle_deg=3,
ndigits=2,
unit=unit,
stroke_width=1,
fill_opacity=0.3)
d = json.loads(geojson)
len_features=len(d['features'])
if not data:
data.append(d)
else:
for i in range(len(d['features'])):
data[0]['features'].append(d['features'][i])
weather_json = json.loads(geojson)
###### Create the DataFrame #####
lats = [30,33,35,40]
lons = [-92,-94,-96,-100]
dat = [1000,2000,500,12500]
df = pd.DataFrame(list(zip(lats,lons,dat)), columns = ['lat', 'lon', 'data'])
##### Add the two on top of on another in a Dash Mapbox #####
# reading in the geospatial data for the state boundaries
with open('States_20m.geojson') as g:
states_json = json.load(g)
column = "data"
fig = px.density_mapbox(
df,
lat="lat",
lon="lon",
z=column,
hover_data={
"lat": True, # remove from hover data
"lon": True, # remove from hover data
column: True,
},
center=dict(lat=38.5, lon=-96),
zoom=3,
radius=30,
opacity=0.4,
mapbox_style="carto-positron",
color_continuous_scale=['rgb(0,0,0)',
'rgb(19,48,239)',
'rgb(115,249,253)',
'rgb(114,245,77)',
'rgb(254,251,84)',
'rgb(235,70,38)'],
range_color = [0, 2000]
)
# Weather outlines
fig.update_layout(
mapbox={
"layers": [
{
"source": f,
"line": {"width":1},
# "type":"line",
"type":"fill",
"color": f["properties"]["fill"],
"opacity": 1,
}
for f in weather_json["features"]
],
}
)
# States outlines
fig.update_layout(
mapbox={
"layers": [
{
"source": g,
"line": {"width":1},
"type":"line",
"color": 'black',
"opacity": 0.5,
}
for g in states_json["features"]
],
}
)
fig.show()
Think of each weather contour as a polygon and the outline of the US as another polygon. What you need is the overlap of the US polygon with each contour polygon.
import pandas as pd
from datetime import datetime
import plotly.express as px
import plotly.graph_objects as go
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.interpolate import griddata,RectSphereBivariateSpline,Rbf
import geojsoncontour
import json
import branca
import scipy as sp
import scipy.ndimage
from geojson import Feature, Polygon, dump
import geopandas as gpd
from urllib.request import urlopen
import shapely.geometry
from shapely.geometry import Point, Polygon, GeometryCollection, Polygon, mapping
from shapely.ops import unary_union
##### Load in the main DataFrame and define vars#####
df = pd.read_csv(r'https://raw.githubusercontent.com/jkiefn1/SO_Json_Question/main/date_data.csv', index_col=[0])
col = 'Day_Temp'
temp_levels = [-20,0,10,20,32]
levels = temp_levels
unit = 'deg F'
colors = ['#f0ffff','#add8e6','#7bc8f6','#069af6','#0343df']
##### Create the weather contour #####
data = []
df_copy = df.copy()
##### Create the GEOJSON Layer #####
vmin = 0
vmax = 1
cm = branca.colormap.LinearColormap(colors, vmin=vmin, vmax=vmax).to_step(len(levels))
x_orig = (df_copy.long.values.tolist())
y_orig = (df_copy.lat.values.tolist())
z_orig = np.asarray(df_copy[col].values.tolist())
x_arr = np.linspace(np.min(x_orig), np.max(x_orig), 5000)
y_arr = np.linspace(np.min(y_orig), np.max(y_orig), 5000)
x_mesh, y_mesh = np.meshgrid(x_arr, y_arr)
xscale = df_copy.long.max() - df_copy.long.min()
yscale = df_copy.lat.max() - df_copy.lat.min()
scale = np.array([xscale, yscale])
z_mesh = griddata((x_orig, y_orig), z_orig, (x_mesh, y_mesh), method='linear')
sigma = [5, 5]
z_mesh = sp.ndimage.filters.gaussian_filter(z_mesh, sigma, mode='nearest')
# Create the contour
contourf = plt.contourf(x_mesh, y_mesh, z_mesh, levels, alpha=0.9, colors=colors,
linestyles='none', vmin=vmin, vmax=vmax)
# Convert matplotlib contourf to geojson
geojson = geojsoncontour.contourf_to_geojson(
contourf=contourf,
min_angle_deg=3,
ndigits=2,
unit=unit,
stroke_width=1,
fill_opacity=0.3)
d = json.loads(geojson)
len_features=len(d['features'])
if not data:
data.append(d)
else:
for i in range(len(d['features'])):
data[0]['features'].append(d['features'][i])
weather_json = json.loads(geojson)
###### Create the DataFrame #####
lats = [30,33,35,40]
lons = [-92,-94,-96,-100]
dat = [1000,2000,500,12500]
df = pd.DataFrame(list(zip(lats,lons,dat)), columns = ['lat', 'lon', 'data'])
##### Add the two on top of on another in a Dash Mapbox #####
# reading in the geospatial data for the state boundaries
states_json = json.loads(urlopen(r'https://raw.githubusercontent.com/jkiefn1/SO_Json_Question/main/States_20m.geojson').read())
#creating outline of the US by joining state outlines into one multipolygon
usa = gpd.GeoDataFrame.from_features(states_json['features'])
usa_poly = gpd.GeoSeries(unary_union(usa['geometry'])).iloc[0]
#geojson to geopandas
gdf = gpd.GeoDataFrame.from_features(weather_json['features'])
#overlapping intersection of US poly with each contour
gdf['inter'] = gdf['geometry'].buffer(0).intersection(usa_poly)
#update weather_json
for n in range(len(gdf)):
weather_json['features'][n]['geometry'] = mapping(gdf['inter'].iloc[n])
column = "data"
fig = px.density_mapbox(
df,
lat="lat",
lon="lon",
z=column,
hover_data={
"lat": True, # remove from hover data
"lon": True, # remove from hover data
column: True,
},
center=dict(lat=38.5, lon=-96),
zoom=3,
radius=30,
opacity=0.4,
mapbox_style="carto-positron",
color_continuous_scale=['rgb(0,0,0)',
'rgb(19,48,239)',
'rgb(115,249,253)',
'rgb(114,245,77)',
'rgb(254,251,84)',
'rgb(235,70,38)'],
range_color = [0, 2000]
)
# Weather outlines
fig.update_layout(
mapbox={
"layers": [
{
"source": f,
"line": {"width":1},
"type":"line",
"type":"fill",
"color": f["properties"]["fill"],
"opacity": 1,
}
for f in weather_json["features"]
],
}
)
# States outlines
fig.update_layout(
mapbox={
"layers": [
{
"source": g,
"line": {"width":1},
"type":"line",
"color": 'black',
"opacity": 0.5,
}
for g in states_json["features"]
],
}
)
fig.show()
I would load the states_20m.geojson into a geodataframe. Then load the weather data into a geodataframe but set the parameter mask equal to the state geodataframe.
states=geopandas.read_file("states_20m.geojson")
weatherDF=pd.read_csv("date_data.csv")
weatherGeo=gpd.GeoDataFrame(weatherDF, geometry= gpd.points_from_xy(weatherDF['long'], weatherDF['lat'], crs="INSERT_CRS_HERE"))
weatherUS=weatherGeo.clip(states)
Hopefully this works!
I am trying to display 3 sets of X/Y coordinates on an animated plotly scatter graph in which the animation key is time. Currently my workaround was to add all the coordinate sets into the same dataframe however I believe this will cause me problems as I need to change marker properties to easily distinguish between each point.
This is what my workaround looks like:
This is how I am generating the graph:
x1_trim += x2_trim
x1_trim += x3_trim
y1_trim += y2_trim
y1_trim += y3_trim
d = {
"x1": x1_trim,
"y1": y1_trim,
"time": time_trim
}
df = pd.DataFrame(d)
#Default x and y axis
x_range = [-1,1]
y_range = [-1,1]
fig = px.scatter(df, x="x1", y="y1", animation_frame="time", range_x=x_range, range_y=y_range)
fig.add_shape(type="rect",x0=-0.5, y0=-0.5, x1=0.5, y1=0.5, line=dict(color="Green",width=2))
As you can see I'm adding my x2/y2 and x3/y3 data onto the end of my x1/y1 list, how would I keep these separate whilst still having all the information on my animated plot? I was trying to display multiple scatter graphs on the same plot however never managed to get it working.
My solution attempt:
#Building the dataframe and drawing graph
d1 = {
"x": x1_trim,
"y": y1_trim,
"time": time_trim
}
d2 = {
"x": x2_trim,
"y": y2_trim,
"time":time_trim
}
d3 = {
"x": x3_trim,
"y": y3_trim,
"time": time_trim
}
dfs = {"d1": d1, "d2": d2, "d3": d3}
fig = go.Figure()
for i in dfs:
fig = fig.add_trace(go.Scatter(x = dfs[i]["x"], y = dfs[i]["y"], name = i, animation_frame=dfs[0]["time"] ))
this can be done very simply in a single data frame
x and y are float values
time string representation of time for animation frame
trace string representation of x/y pair corresponding to x1/y1, x2/y2 etc
using Plotly Express is then very simple
have included sample data to show how simple this structure is
comments requests that a different symbol is used for each of the traces. https://plotly.com/python/marker-style/#custom-marker-symbols Plotly: How to set marker symbol shapes for multiple traces using plotly express?
import numpy as np
import itertools
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
# construct a dataframe that has four columns, x, y, trace: 0,1,2 as string for trace, time for animation frame
df = pd.DataFrame(np.random.uniform(1, 5, [288, 2]), columns=["x", "y"]).join(
pd.DataFrame(
itertools.product(
pd.Series(
pd.date_range("1-jan-2021", freq="1H", periods=24 * 4)
).dt.time.astype(str),
range(3),
),
columns=["time", "trace"],
).astype(str)
)
fig = px.scatter(
df.assign(size=8),
x="x",
y="y",
color="trace",
animation_frame="time",
symbol="trace",
size="size",
size_max=10,
)
fig
data frame structure
x float64
y float64
time object
trace object
dtype: object
raw data
per comments - construction of data frame has a number of techniques that are not core to solution
this shows just two times sample data and being loaded into a data frame
import io
df = pd.read_csv(io.StringIO("""x,y,time,trace
4.47189433943762,2.2663279945423125,00:00:00,0
3.263751615344729,2.7707896475420433,00:00:00,1
3.5073083888118197,3.937926244743114,00:00:00,2
3.254552306893224,1.7740014652497695,01:00:00,0
1.6111813732639115,1.5324478432794377,01:00:00,1
3.411314175447148,4.495634466903654,01:00:00,2
1.7036170024927264,4.284719804413246,00:00:00,0
1.9797441059531726,3.9012400136550798,00:00:00,1
1.5178030860172549,3.7904674709011084,00:00:00,2
2.03612601506845,2.5378053661978592,01:00:00,0
2.230688800088902,2.946463794148376,01:00:00,1
1.3626620551885207,2.442489690168825,01:00:00,2
4.733618949813925,3.9103378744051014,00:00:00,0
4.4816142771548435,1.1245335267028125,00:00:00,1
4.9550805577829315,3.2454665809417227,00:00:00,2
2.9007566994079816,1.1620429771047482,01:00:00,0
2.11807366926913,3.9811777626521083,01:00:00,1
2.0753910017252144,4.416934286540347,01:00:00,2
2.3867481776916804,1.6378885254464284,00:00:00,0
1.4021710772900526,2.1565431787254536,00:00:00,1
3.5150580308648562,2.2722969079838387,00:00:00,2
4.987010605760303,1.943335174662026,01:00:00,0
3.0504403251471484,4.398673922531113,01:00:00,1
4.021398175417694,4.422199058284852,01:00:00,2"""))
df["trace"] = df["trace"].astype(str)
px.scatter(df, x="x", y="y", color="trace", animation_frame="time")
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
My goal is to create an animation with my 3D data in plotly.
I have 3 variables x,y,z for simplicity and I plot the 4th value depending on these x,y,z.
I create a 3D scatter plot where the 4th dim sort to speak is the color like this:
from numpy import genfromtxt
import numpy as np
import plotly.io as pio
import plotly.express as px
pio.renderers.default = 'notebook'
import plotly.graph_objects as go
import math
import pandas as pd
data = pd.read_csv("paramtp_1e-05_big.txt")
data.head()
data = data.iloc[::10, :]
color_data = data['gopt'].astype(float).round(decimals=2)
color_data[color_data>= 10] = 10
color_data_nopt = data['nopt'].astype(float).round(decimals=3)
color_data_mc = data['mc'].astype(float).round(decimals=3)
color_data_P= data['P']
color_data_P[color_data_P >= 1] = 1
data= data.replace(np.nan, '', regex=True)
data.tail()
fig = px.scatter_3d(data, x='NpN0', y='s', z='mu',log_x=True, log_z=True,
opacity = 0.5,
color=color_data,color_continuous_scale=px.colors.sequential.Viridis)
fig.add_trace(
go.Scatter(
mode='markers',
marker=dict(
size=1,
opacity=0.5,
),
)
)
fig.show()
Similarly to this wonderful animation: https://plotly.com/python/visualizing-mri-volume-slices/
I would like to slice up my data to isosurfaces with respect to any x,y,z coordinates.
As in the example they use images, I could not wrap my head around to create the same with my raw data.
Thank you in advance.