Reputation: 83
This is a question that derives from a previous one, which can be found here: Multiple opacities in Mapbox - Plotly for Python
I was having some issues regarding having multiple opacities for multiple traces on a Mapbox map, each opacity associated with a particular value of the dataframe.
After following Rob's helpful answer (see link above), which led to the solution on the first topic, I found myself in another problem, as I want now kind of twist on his code.
While my first approach was to only have variations on the opacity of the traces, I would now need to have both variations on the opacity and the width of each trace, corresponding each of them to a different value on a particular dataframe.
I proceed to copy-paste the first part of Rob's answer, where he declares some functions and creates a dataframe:
import requests
import geopandas as gpd
import plotly.graph_objects as go
import itertools
import numpy as np
import pandas as pd
from pathlib import Path
# get geometry of london underground stations
gdf = gpd.GeoDataFrame.from_features(
requests.get(
"https://raw.githubusercontent.com/oobrien/vis/master/tube/data/tfl_stations.json"
).json()
)
# limit to zone 1 and stations that have larger number of lines going through them
gdf = gdf.loc[gdf["zone"].isin(["1","2","3","4","5","6"]) & gdf["lines"].apply(len).gt(0)].reset_index(
drop=True
).rename(columns={"id":"tfl_id", "name":"id"})
# wanna join all valid combinations of stations...
combis = np.array(list(itertools.combinations(gdf.index, 2)))
# generate dataframe of all combinations of stations
gdf_c = (
gdf.loc[combis[:, 0], ["geometry", "id"]]
.assign(right=combis[:, 1])
.merge(gdf.loc[:, ["geometry", "id"]], left_on="right", right_index=True, suffixes=("_start_station","_end_station"))
)
gdf_c["lat_start_station"] = gdf_c["geometry_start_station"].apply(lambda g: g.y)
gdf_c["long_start_station"] = gdf_c["geometry_start_station"].apply(lambda g: g.x)
gdf_c["lat_end_station"] = gdf_c["geometry_end_station"].apply(lambda g: g.y)
gdf_c["long_end_station"] = gdf_c["geometry_end_station"].apply(lambda g: g.x)
gdf_c = gdf_c.drop(
columns=[
"geometry_start_station",
"right",
"geometry_end_station",
]
).assign(number_of_journeys=np.random.randint(1,10**5,len(gdf_c)))
gdf_c
f = Path.cwd().joinpath("SO.csv")
gdf_c.to_csv(f, index=False)
# there's an requirement to start with a CSV even though no sample data has been provided, now we're starting with a CSV
df = pd.read_csv(f)
# makes use of ravel simpler...
df["none"] = None
My particular problem starts here: when trying to create two "loops" (one for opacity, and one for width), I thought I could do the following:
BINS_FOR_OPACITY=10
opacity_a = np.geomspace(0.001,1, BINS_FOR_OPACITY)
BINS_FOR_WIDTH=10
width_a = np.geomspace(1,3, BINS_FOR_WIDTH)
fig = go.Figure()
# Note the double "for" statement that follows
for opacity, d in df.groupby(pd.cut(df["number_of_journeys"], bins=BINS_FOR_OPACITY, labels=opacity_a)):
for width, d in df.groupby(pd.cut(df["number_of_journeys"], bins=BINS_FOR_WIDTH, labels=width_a)):
fig.add_traces(
go.Scattermapbox(
name=f"{d['number_of_journeys'].mean():.2E}",
lat=np.ravel(d.loc[:,[c for c in df.columns if "lat" in c or c=="none"]].values),
lon=np.ravel(d.loc[:,[c for c in df.columns if "long" in c or c=="none"]].values),
line_width=width
line_color="blue",
opacity=opacity,
mode="lines+markers",
)
)
[Rob's original answer had only the first for
statement, and not the second one]
However, the above is clearly not working, as it is making much more traces than it should do (I really can't explain why, but I guess it might be because of the double loop forced by the two for
statements).
It ocurred to me that some kind of solution could be hidding in the pd.cut
part, as I would need something like a double cut, but couldn't find a way to properly doing it.
I also managed to create a Pandas series by:
widths = pd.cut(df.["size"], bins=BINS_FOR_WIDTH, labels=width_a)
and iterating over that series, but got the same result as before (an excess of traces).
To emphasize and clarify myself, I don't need to have only multiple opacities or multiple widths, but I need to have them both and at the same time, which is what's causing me some troubles.
I am deeply grateful for any help
Upvotes: 0
Views: 112
Reputation: 31226
width_a
as belowopacity
and width
will move in step as it's cutting same column by same number of binsBINS = 10
opacity_a = np.geomspace(0.001, 1, BINS)
width_a = np.linspace(0.1, 2, BINS)
fig = go.Figure()
for (opacity, width), d in df.groupby(
[
pd.cut(df["number_of_journeys"], bins=BINS, labels=opacity_a),
pd.cut(df["number_of_journeys"], bins=BINS, labels=width_a),
]
):
fig.add_traces(
go.Scattermapbox(
name=f"{d['number_of_journeys'].mean():.2E}",
lat=np.ravel(
d.loc[:, [c for c in df.columns if "lat" in c or c == "none"]].values
),
lon=np.ravel(
d.loc[:, [c for c in df.columns if "long" in c or c == "none"]].values
),
line_color="blue",
line_width=width,
opacity=opacity,
mode="lines+markers",
)
)
fig.update_layout(
mapbox={
"style": "carto-positron",
"center": {"lat": 51.520214996769255, "lon": -0.097792388774743},
"zoom": 9,
},
margin={"l": 0, "r": 0, "t": 0, "b": 0},
)
Upvotes: 1