matplotlib 如何从多个绘图调用创建两个不同的图例

5fjcxozz  于 2023-06-23  发布在  其他
关注(0)|答案(3)|浏览(115)

我必须创建一个可视化,其中有多个线图(趋势线/移动平均线等)和多个散点图。
我已经成功地创建了一个单一的图中的所有图表,但我希望散点图的图例是从线图不同。
我希望线图的图例位于图的中心,散点图的图例位于图的左上角。
4个散点图的代码

#Scatter plot
#Using matplotlib scatter plot
scatter1 = plt.scatter(data=df[df.ABC < 10], x='Date', y='ABC', c = 'cyan' , s = 5, label='less')
scatter2 = plt.scatter(data=df[(df.ABC >= 10) & (df.ABC < 40)], x='Date', y='ABC', c = 'dodgerblue' , s = 5, label='normal')
scatter3 = plt.scatter(data=df[(df.ABC >= 40) & (df.ABC < 60)], x='Date', y='ABC', c = 'orangered' , s = 5, label='good')
scatter4 = plt.scatter(data=df[df.ABC > 60], x='Date', y='ABC', c = 'brown' , s = 5, label='more')

折线图和计算指标的代码(必须位于同一图例框中)

#Empty legend line specifying a calulated metric
plt.plot([], [], ' ', label=f'Points above Target Budget PR = {num}/{den} = {ans}%')

#plot using rolling average
#using seaborn.lineplot()
sns.lineplot( x = 'Date',
             y = 'moving avg',
             data = df,
             color='red',
             linewidth=1.5,
             label = 'Moving Average')

#plot a simple time series plot
#using seaborn.lineplot()
sns.lineplot( x = 'Date',
             y = 'Yield',
             color = 'darkgreen',
             linewidth = 1.5,
             data = df,
             label = 'Yield')

我尝试了以下方法来制作多个图例:

legend1 = plt.legend((scatter1,scatter2,scatter3,scatter4),['less','normal','good','more'], loc = 'upper left')
plt.gca().add_artist(legend1)
plt.legend(loc = 'center')

上面的代码所做的是,它在左上角为散点图创建一个图例。然而,相同的点被重写在图例中,该图例应该仅包含线图和计算的度量。因此散点图的图例在图表中表示两次。
我也试着在代码中做了其他的修改,但是结果总是导致1个图例包括全部,或者2个图例有散点图部分重复,或者根本没有图例。

cl25kdpy

cl25kdpy1#

  • 在进行任何可视化之前管理数据通常是更好的选择。

1.使用pd.cut将数据与类别绑定
1.将滚动平均值和中位数作为列添加到数据框架中
1.使用pandas.DataFrame.melt将新列转换为长格式

  • 这样更好,因为所有的数据都在一个地方,并且它允许更容易地可视化数据。
  • 从视觉上讲,除非有两个不同的y轴(左/右),否则它会创建一个更清晰的可视化,以具有单个图例,而不是在图的中间,或以其他方式覆盖标记。
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

# create sample data
np.random.seed(2023)
df=pd.DataFrame({'ABC': np.random.uniform(low=0, high=100, size=(100,)),
                 'Date': pd.date_range('2019/07/01', periods=100, freq='SM')})

# create bins with labels for the data
bins = [-np.inf, 10, 40, 60, np.inf]
labels = ['less', 'normal', 'good', 'more']
df['cats'] = pd.cut(df.ABC, bins=bins, labels=labels, ordered=True, right=False)

# add the rolling mean and median as a column
df['MA'] = df.ABC.rolling(10).mean()
df['Yield'] = df.ABC.rolling(10).median()

# convert the rolling columns to a long form
df = df.melt(id_vars=['ABC', 'Date', 'cats'], var_name='Labels')

# plot
fig = plt.figure(figsize=(10, 6))
ax = sns.scatterplot(data=df, x='Date', y='ABC', hue='cats', palette=['cyan', 'dodgerblue', 'orangered', 'brown'])
sns.lineplot(data=df, x='Date', y='value', hue='Labels', palette=['red', 'darkgreen'], ax=ax)

sns.move_legend(ax, bbox_to_anchor=(1, 0.5), loc='center left', frameon=False)

df

ABC       Date    cats Labels      value
0    32.198830 2019-07-15  normal     MA        NaN
1    89.042245 2019-07-31    more     MA        NaN
2    58.805226 2019-08-15    good     MA        NaN
3    12.659609 2019-08-31  normal     MA        NaN
4    14.134122 2019-09-15  normal     MA        NaN
...
195  60.740918 2023-06-30    more  Yield  61.721929
196  47.481600 2023-07-15    good  Yield  56.303730
197  70.959070 2023-07-31    more  Yield  61.721929
198  11.524271 2023-08-15  normal  Yield  56.303730
199  73.279407 2023-08-31    more  Yield  56.303730
eiee3dmh

eiee3dmh2#

之前发布的优秀答案大多是针对OP问题的-我想添加一个通用的解决方案来解决这个问题,即使用每个艺术家类型的图例是在情节中存在的。
首先,我们需要一个函数从句柄中提取相同类型的艺术家

def split(handles_labels, plot_type):
    import matplotlib

    types = dict(plot=matplotlib.lines.Line2D,
                 scatter=matplotlib.collections.PathCollection,)

    try: plot_type = types[plot_type]
    except KeyError: raise ValueError('Invalid plot type.')

    return zip(*((h, l) for h, l in zip(*handles_labels) if type(h) is plot_type))

那么就很容易产生上面的情节……

import matplotlib.pyplot as plt

fig, ax = plt.subplots(layout='constrained')

ax.scatter(9, 4, label='A')
ax.plot((5,7),(4,8), label='B')
ax.scatter(3, 7, label='C')
ax.plot((4,8),(6,5), label='D')

handles_labels = ax.get_legend_handles_labels()

l0 = ax.legend(*split(handles_labels, 'scatter'), loc='upper left')
ax.add_artist(l0)
l1 = ax.legend(*split(handles_labels, 'plot'), loc='upper right')

plt.show()
cunj1qz1

cunj1qz13#

一种方法来做到这一点(我认为是更简单的方法)是简单地完成绘图,并得到所有6个条目的图例。然后使用get_legend_handles_labels()获取图例和句柄,根据需要将它们拆分,并将它们创建为单独的图例。注意add_artist是为了将第一个添加到轴上。整个代码在下面…主要是你所拥有的,删除了你所拥有的空图例行,大部分添加的代码都是朝着底部的...此外,Title 1和Title 2是可选的,您可以根据需要使用/删除它。

df=pd.DataFrame({'ABC':np.random.uniform(low=0, high=100, size=(100,)),
                'Date':pd.date_range('2019/07/01', periods=100, freq='SM')})
#Using matplotlib scatter plot
scatter1 = plt.scatter(data=df[df.ABC < 10], x='Date', y='ABC', c = 'cyan' , s = 5, label='less')
scatter2 = plt.scatter(data=df[(df.ABC >= 10) & (df.ABC < 40)], x='Date', y='ABC', c = 'dodgerblue' , s = 5, label='normal')
scatter3 = plt.scatter(data=df[(df.ABC >= 40) & (df.ABC < 60)], x='Date', y='ABC', c = 'orangered' , s = 5, label='good')
scatter4 = plt.scatter(data=df[df.ABC > 60], x='Date', y='ABC', c = 'brown' , s = 5, label='more')

#Empty legend line specifying a calulated metric
#plt.plot([], [], ' ', label=f'Points above Target Budget PR = {num}/{den} = {ans}%')

#plot using rolling average
#using seaborn.lineplot()
sns.lineplot( x = 'Date',
             y = df.ABC.rolling(10).mean(),
             data = df,
             color='red',
             linewidth=1.5,
             label = 'Moving Average')

#plot a simple time series plot
#using seaborn.lineplot()
sns.lineplot( x = 'Date',
             y = df.ABC.rolling(10).median(),
             color = 'darkgreen',
             linewidth = 1.5,
             data = df,
             label = 'Yield')

h,l = plt.gca().get_legend_handles_labels() ##Get the legend handles and lables

l1 = plt.gca().legend(h[:2],l[:2], loc='center', title='Title 1') ##Plot the seborn lines as the first legend
l2 = plt.gca().legend(h[2:],l[2:], loc='upper right', title='Title 2') ##Plot the seborn lines as the first legend

plt.gca().add_artist(l1) # 2nd legend will erases the first, so need to add it
plt.show()

相关问题