Mapbox 中的多个不透明度 - Plotly for Python
Posted
技术标签:
【中文标题】Mapbox 中的多个不透明度 - Plotly for Python【英文标题】:Multiple opacities in Mapbox - Plotly for Python 【发布时间】:2022-01-17 22:26:09 【问题描述】:我目前正在从事数据可视化项目。
我想绘制多条线(大约 200k)代表从一个地铁站到所有其他地铁站的行程。也就是说,所有的地铁站都应该是一条直线。
线条的颜色并不重要(很可能是红色、蓝色等),但最重要的是不透明度。两个随机站点之间的行程次数越多,该特定线路的不透明度就越高;反之亦然。
我觉得我已经接近所需的输出,但无法找到正确的方法。
我使用的DataFrame(df = pd.read_csv(...)
)由一系列列组成,即:id_start_station
、id_end_station
、lat_start_station
、long_start_station
、lat_end_station
、long_end_station
、number_of_journeys
。
我必须通过编码来提取坐标
lons = []
lons = np.empty(3 * len(df))
lons[::3] = df['long_start_station']
lons[1::3] = df['long_end_station']
lons[2::3] = None
lats = []
lats = np.empty(3 * len(df))
lats[::3] = df['lat_start_station']
lats[1::3] = df['lat_end_station']
lats[2::3] = None
然后我开始了一个图:
fig = go.Figure()
然后通过以下方式添加跟踪:
fig.add_trace(go.Scattermapbox(
name='Journeys',
lat=lats,
lon=lons,
mode='lines',
line=dict(color='red', width=1),
opacity= ¿?, # PROBLEM IS HERE [1]
))
[1] 所以我尝试了几种不同的方法来传递不透明项:
-
我为每条轨迹的不透明度创建了一个新元组,作者:
opacity = []
opacity = np.empty(3 * len(df))
opacity [::3] = df['number_of_journeys'] / max(df['number_of_journeys'])
opacity [1::3] = df['number_of_journeys'] / max(df['number_of_journeys'])
opacity [2::3] = None
并将其传递给[1],但是出现了这个错误:
ValueError:
Invalid value of type 'numpy.ndarray' received for the 'opacity' property of scattermapbox
The 'opacity' property is a number and may be specified as:
- An int or float in the interval [0, 1]
-
然后我想通过使用
rgba
的属性alpha
将“不透明度”术语传递给“颜色”术语,例如:rgba(255,0,0,0.5)
。
所以我首先创建了所有alpha
参数的“映射”:
df['alpha'] = df['number_of_journeys'] / max(df['number_of_journeys'])
然后创建一个函数来检索特定颜色内的所有alpha
参数:
colors_with_opacity = []
def colors_with_opacity_func(df, empty_list):
for alpha in df['alpha']:
empty_list.extend(["rgba(255,0,0,"+str(alpha)+")"])
empty_list.extend(["rgba(255,0,0,"+str(alpha)+")"])
empty_list.append(None)
colors_with_opacity_func(df, colors_with_opacity)
并将其传递到Scattermapbox的颜色属性中,但出现以下错误:
ValueError:
Invalid value of type 'builtins.list' received for the 'color' property of scattermapbox.line
The 'color' property is a color and may be specified as:
- A hex string (e.g. '#ff0000')
- An rgb/rgba string (e.g. 'rgb(255,0,0)')
- An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
- An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
- A named CSS color:
aliceblue, antiquewhite, aqua, [...] , whitesmoke,
yellow, yellowgreen
由于它是大量的行,循环/迭代跟踪将执行性能问题。
任何帮助将不胜感激。我想不出一种方法来正确地完成它。
提前谢谢你。
编辑 1:添加了新问题
我在下面添加这个问题,因为我相信它可以帮助其他正在寻找这个特定主题的人。
按照 Rob 的有用回答,我设法添加了多个不透明度,如前所述。
但是,我的一些同事提出了一项可以改进地图可视化的更改。
现在,我不想拥有多个不透明度(每个跟踪一个,根据数据框的值)也希望有多个宽度(根据数据框的相同值)。
这是,按照 Rob 的回答,我需要这样的东西:
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",
)
)
但是,上面的方法显然不起作用,因为它比它应该做的跟踪要多得多(我真的无法解释为什么,但我想这可能是因为两个for
强制执行的双循环声明)。
我发现某种解决方案可能隐藏在 pd.cut
部分中,因为我需要 类似 的双重切割,但找不到正确执行此操作的方法.
我还设法通过以下方式创建了熊猫系列:
widths = pd.cut(df.["size"], bins=BINS_FOR_WIDTH, labels=width_a)
并迭代该系列,但得到与以前相同的结果(过多的痕迹)。
为了强调和澄清我自己,我不需要只有多个不透明度或多个宽度,但我需要同时和在同时,这就是给我带来一些麻烦的原因。
再次感谢任何帮助。
【问题讨论】:
【参考方案1】:opacity
是每条迹线,对于标记,可以使用 rgba(a,b,c,d)
用颜色完成,但不能用于线条。 (在直散点图中相同)
为了演示,我使用了伦敦地铁站(经过过滤以减少节点数量)。加上将数据格式化为 CSV 的额外努力。 JSON作为源与解决方案无关
编码到 bin number_of_journeys 以包含在用于计算和不透明度的几何级数的轨迹中
此样本数据集正在生成 83k 样本行
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
# now it's simple to generate scattermapbox... a trace per required opacity
BINS=10
opacity_a = np.geomspace(0.001,1, BINS)
fig = go.Figure()
for opacity, d in df.groupby(pd.cut(df["number_of_journeys"], bins=BINS, labels=opacity_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",
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,
)
【讨论】:
感谢您的回复!我确实了解这些步骤,但我目前没有使用 JSON 文件;我正在使用 .csv,它有超过 20 万行。尝试“加入所有有效的站组合”(第 3 步)时,Python 崩溃(由于 RAM 内存使用)。再次感谢您的帮助! 已更新 - btw 静态数据序列化格式与您如何使用 plotly(即 JSON 或 CSV)无关。鉴于您的源数据已经是对,我不明白为什么您会生成对以生成超过 20 亿个组合。 SO最佳实践是提供示例数据是有原因的......那么你就不会误解生成一些数据与实际活动代码的步骤...... 谢谢你,@rob!它工作得非常好。非常聪明的举动。再次谢谢你! PS:我无法上传 .csv 的示例数据,因为它是一个私有文件(即没有指向该文件的链接/url)。我现在意识到我的帖子可能太混乱了(对此感到抱歉)。 很好用 ***.com/help/someone-answers PS 我在一家瑞士银行工作了 20 年,所以完全理解数据保密性....您总是可以像我一样从公开可用的数据中生成示例参考数据集在回答 你好@RobRaymond。我很抱歉带回这个话题。我希望你能检查一下我刚刚在这个问题上发布的 EDIT 1。在我的几个同事的建议下,我添加了一个与原始问题密切相关的新问题。再次感谢您的所有帮助与合作。新年快乐!以上是关于Mapbox 中的多个不透明度 - Plotly for Python的主要内容,如果未能解决你的问题,请参考以下文章
在 Plotly Density_Mapbox 上添加 GeoJSON 等高线作为图层
在 Plotly 的 density_mapbox 可视化中更新底图,同时保留视口