Plotly - “grouped” 散点图

4
假设我有以下的pandas数据框:
import pandas as pd
d = {'Person': ['Bob']*9 + ['Alice']*9,
    'Time': ['Morining']*3 + ['Noon']*3 + ['Evening']*3 + ['Morining']*3 + ['Noon']*3 + ['Evening']*3,
    'Color': ['Red','Blue','Green']*6,
    'Energy': [1,5,4,7,3,6,8,4,2,9,8,5,2,6,7,3,8,1]}
df = pd.DataFrame(d)

enter image description here

我该如何创建类似于这样的图表? enter image description here (请忽略粗糙的绘图)
我尝试使用散点图、条形图和箱线图来实现,但都没有成功。
谢谢!
4个回答

3
  • 为每个生成一个散点图轨迹
  • x进行一些逻辑处理,以便每个人都有偏移。因此具有hovertextxaxis刻度
import plotly.graph_objects as go

xbase = pd.Series(df["Time"].unique()).reset_index().rename(columns={"index":"x",0:"Time"})
dfp = df.merge(xbase, on="Time").set_index("Person")

go.Figure(
    [
        go.Scatter(
            name=p,
            x=dfp.loc[p, "x"] + i/10,
            y=dfp.loc[p, "Energy"],
            text=dfp.loc[p, "Time"],
            mode="markers",
            marker={"color": dfp.loc[p, "Color"], "symbol":i, "size":10},
            hovertemplate="(%{text},%{y})"
        )
        for i, p in enumerate(dfp.index.get_level_values("Person").unique())
    ]
).update_layout(xaxis={"tickmode":"array", "tickvals":xbase["x"], "ticktext":xbase["Time"]})

enter image description here


谢谢!如果我还想让颜色显示在图例中怎么办? - soungalo
我倾向于添加三个虚拟跟踪器...假设总是有三种颜色,每种颜色都有一致的含义。 - Rob Raymond

2

您已经收到了一些不错的建议,但由于您仍在疑惑:

如果我还想在图例中显示颜色怎么办?

我只想说,px.scatter 函数已经非常接近理想的结果了。唯一缺少的是 jitter。但是,以下几行代码就可以产生下面的图形:

fig = px.scatter(df, x = 'Time', y = 'Energy', color = 'Color', symbol = 'Person')

fig.for_each_trace(lambda t: t.update(marker_color = t.name.split(',')[0],
                                      name = t.name.split(',')[1], x = [1,2,3]))

fig.for_each_trace(lambda t: t.update(x=tuple([x + 0.2 for x in list(t.x)])) if t.name == ' Alice' else ())

这里输入图片描述

完整代码:

import pandas as pd
import plotly.express as px
import plotly.graph_objs as go

# data
d = {'Person': ['Bob']*9 + ['Alice']*9,
    'Time': ['Morining']*3 + ['Noon']*3 + ['Evening']*3 + ['Morning']*3 + ['Noon']*3 + ['Evening']*3,
    'Color': ['Red','Blue','Green']*6,
    'Energy': [1,5,4,7,3,6,8,4,2,9,8,5,2,6,7,3,8,1]}
df = pd.DataFrame(d)

# figure setup
fig = px.scatter(df, x = 'Time', y = 'Energy', color = 'Color', symbol = 'Person')

# some customizations in order to get to the desired result:
fig.for_each_trace(lambda t: t.update(marker_color = t.name.split(',')[0],
                                      name = t.name.split(',')[1],
                                     x = [1,2,3]))
# jitter
fig.for_each_trace(lambda t: t.update(x=tuple([x + 0.2 for x in list(t.x)])) if t.name == ' Alice' else ())


# layout
fig.update_layout(xaxis={"tickmode":"array","tickvals":[1,2,3],"ticktext":df.Time.unique()})
    
fig.show()

改进空间:

上面代码片段的一些元素无疑可以更加动态化,比如x = [1,2,3]应该考虑到x轴上的可变元素数量。对于人数和jitter使用的参数也是同样的情况。如果这是您需要的内容,我也可以查看这些方面的内容。


1
您可以使用itertuples(比iterrows更好的性能)遍历DataFrame的每一行,并将'Morning','Noon'和'Evening'的值分别映射为1,2,3,然后通过将'Bob'映射为'-0.05'并将'Alice'映射为0.05,将这些值添加到每个x值中来抖动x值。您还可以将“Color”信息传递给marker_color参数。
然后将1,2,3tickvalues映射回'Morning','Noon'和'Evening',并使用legendgroup获取仅显示一个Bob和一个Alice图例标记(以防止每个跟踪的标记在图例中显示)。
import pandas as pd
import plotly.graph_objects as go

d = {'Person': ['Bob']*9 + ['Alice']*9,
    'Time': ['Morning']*3 + ['Noon']*3 + ['Evening']*3 + ['Morning']*3 + ['Noon']*3 + ['Evening']*3,
    'Color': ['Red','Blue','Green']*6,
    'Energy': [1,5,4,7,3,6,8,4,2,9,8,5,2,6,7,3,8,1]}
df = pd.DataFrame(d)

shapes = {'Bob': 'circle', 'Alice': 'diamond'}
time = {'Morning':1, 'Noon':2, 'Evening':3}
jitter = {'Bob': -0.05, 'Alice': 0.05}

fig = go.Figure()
## position 1 of each row is Person... position 4 is the Energy value
s = df.Person.shift() != df.Person
name_changes = s[s].index.values
for row in df.itertuples():
    if row[0] in name_changes:
        fig.add_trace(go.Scatter(
            x=[time[row[2]] + jitter[row[1]]],
            y=[row[4]],
            legendgroup=row[1],
            name=row[1],
            mode='markers',
            marker_symbol=shapes[row[1]],
            marker_color=row[3],
            showlegend=True
        ))
    else:
        fig.add_trace(go.Scatter(
            x=[time[row[2]] + jitter[row[1]]],
            y=[row[4]],
            legendgroup=row[1],
            name=row[1],
            mode='markers',
            marker_symbol=shapes[row[1]],
            marker_color=row[3],
            showlegend=False
        ))

fig.update_traces(marker=dict(size=12,line=dict(width=2,color='DarkSlateGrey')))
fig.update_layout(
    xaxis=dict(
        tickmode='array',
        tickvals=list(time.values()),
        ticktext=list(time.keys())
    )
)
fig.show() 

enter image description here


1

如果您只想使用matplotlib而不需要其他依赖项,这里是一个示例代码。(Pandas操作groupbys等留给您优化)

import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.transforms as transforms
from matplotlib.lines import Line2D

df = pd.DataFrame(
    {
        'Person': ['Bob'] * 9 + ['Alice'] * 9,
        'Time': ['Morning'] * 3
        + ['Noon'] * 3
        + ['Evening'] * 3
        + ['Morning'] * 3
        + ['Noon'] * 3
        + ['Evening'] * 3,
        'Color': ['Red', 'Blue', 'Green'] * 6,
        'Energy': [1, 5, 4, 7, 3, 6, 8, 4, 2, 9, 8, 5, 2, 6, 7, 3, 8, 1],
    }
)

plt.figure()

x = ['Morning', 'Noon', 'Evening']

# Transform function
offset = lambda p: transforms.ScaledTranslation(
    p / 72.0, 0, plt.gcf().dpi_scale_trans
)
trans = plt.gca().transData

# Use this to center transformation
start_offset = -len(df['Person'].unique()) // 2

# Define as many markers as people you have
markers = ['o', '^']

# Use this for custom legend
custom_legend = []

# Do this if you need to aggregate
df = df.groupby(['Person', 'Time', 'Color'])['Energy'].sum().reset_index()

df = df.set_index('Time')
for i, [person, pgroup] in enumerate(df.groupby('Person')):
    pts = (i + start_offset) * 10
    marker = markers[i]
    transform = trans + offset(pts)

    # This is for legend, not plotted
    custom_legend.append(
        Line2D(
            [0],
            [0],
            color='w',
            markerfacecolor='black',
            marker=marker,
            markersize=10,
            label=person,
        )
    )

    for color, cgroup in pgroup.groupby('Color'):
        mornings = cgroup.loc[cgroup.index == 'Morning', 'Energy'].values[0]
        noons = cgroup.loc[cgroup.index == 'Noon', 'Energy'].values[0]
        evenings = cgroup.loc[cgroup.index == 'Evening', 'Energy'].values[0]

        # This stupid if is because you need to define at least one non
        # transformation scatter be it first or whatever.
        if pts == 0:
            plt.scatter(
                x,
                [mornings, noons, evenings],
                c=color.lower(),
                s=25,
                marker=marker,
            )
        else:
            plt.scatter(
                x,
                [mornings, noons, evenings],
                c=color.lower(),
                s=25,
                marker=marker,
                transform=transform,
            )

plt.ylabel('Energy')
plt.xlabel('Time')
plt.legend(handles=custom_legend)
plt.margins(x=0.5)
plt.show()

plooot


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接