Adding legend to a radarchart in Python

别等时光非礼了梦想. 提交于 2019-12-13 07:21:29

问题


I want to create a radarchart with legends for every plot that is created but until now it has been impossible. Then, you can see the code used to create the plot (thanks to another user that developed it).

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns # improves plot aesthetics
import pandas as pd

def _invert(x, limits):
    """inverts a value x on a scale from
    limits[0] to limits[1]"""
    return limits[1] - (x - limits[0])

def _scale_data(data, ranges):
    """scales data[1:] to ranges[0],
    inverts if the scale is reversed"""
    for d, (y1, y2) in zip(data[1:], ranges[1:]):
        assert (y1 <= d <= y2) or (y2 <= d <= y1)
    x1, x2 = ranges[0]
    d = data[0]
    if x1 > x2:
        d = _invert(d, (x1, x2))
        x1, x2 = x2, x1
    sdata = [d]
    for d, (y1, y2) in zip(data[1:], ranges[1:]):
        if y1 > y2:
            d = _invert(d, (y1, y2))
            y1, y2 = y2, y1
        sdata.append((d-y1) / (y2-y1) 
                     * (x2 - x1) + x1)
    return sdata

class ComplexRadar():
    def __init__(self, fig, variables, ranges,
                 n_ordinate_levels=6):
        angles = np.arange(0, 360, 360./len(variables))

        axes = [fig.add_axes([0.1,0.1,0.9,0.9],polar=True,
                label = "axes{}".format(i)) 
                for i in range(len(variables))]
        l, text = axes[0].set_thetagrids(angles, 
                                         labels=variables)
        [txt.set_rotation(angle-90) for txt, angle 
             in zip(text, angles)]
        for ax in axes[1:]:
            ax.patch.set_visible(False)
            ax.grid("off")
            ax.xaxis.set_visible(False)
        for i, ax in enumerate(axes):
            grid = np.linspace(*ranges[i], 
                               num=n_ordinate_levels)
            gridlabel = ["{}".format(round(x,2)) 
                         for x in grid]
            if ranges[i][0] > ranges[i][1]:
                grid = grid[::-1] # hack to invert grid
                          # gridlabels aren't reversed
            gridlabel[0] = "" # clean up origin
            ax.set_rgrids(grid, labels=gridlabel,
                         angle=angles[i])
            #ax.spines["polar"].set_visible(False)
            ax.set_ylim(*ranges[i])
        # variables for plotting
        self.angle = np.deg2rad(np.r_[angles, angles[0]])
        self.ranges = ranges
        self.ax = axes[0]
    def plot(self, data, *args, **kw):
        sdata = _scale_data(data, self.ranges)
        self.ax.plot(self.angle, np.r_[sdata, sdata[0]], *args, **kw)
    def fill(self, data, *args, **kw):
        sdata = _scale_data(data, self.ranges)
        self.ax.fill(self.angle, np.r_[sdata, sdata[0]], *args, **kw)

Every plot represents to different people. I want to add a legend showing the name of everyone and the color that represents him in the plot. I tried to add labels and then the legends but it didn´t work. The plot produced is something like that:

df = pd.DataFrame({
    "Spe": pd.Series([89, 83, 70, 60, 30, 49, 28]),
    "Str": pd.Series([69, 53, 30, 20, 10, 29, 48]),
    "Det": pd.Series([82, 44, 79, 39, 20, 10, 85]),
    "Extr": pd.Series([59, 74, 29, 36, 18, 29, 18]),
    "Int": pd.Series([63, 11, 20, 36, 97, 58, 91]),
    "Est": pd.Series([12, 69, 89, 59, 19, 58, 98]),
    "Ape": pd.Series([29, 13, 94, 30, 20, 10, 67]),
    "ID": pd.Series(["Carl","Michael","Peter","Louis","Sarah",
                    "Laura","Nicholas"])
})
dfo = df.drop('ID', 1)
print(dfo)

variables = ("1", "2", "3", 
        "4", "5", "6", "7")
ranges = [(1,100),(1,100),(1,100),(1,100),(1,100),(1,100),(1,100)] 
fig1 = plt.figure(figsize=(8, 8))
radar = ComplexRadar(fig1, variables, ranges)
for i in range(len(dfo.index)-1):
    data=dfo.iloc[i,:]
    radar.plot(data)
    radar.fill(data,alpha=0.2)
sns.plt.show()

Link to plot produced

Thanks for your help.


回答1:


In case someone is interested, the code of the ComplexRadar comes from here.
The ComplexRadar class uses many axes and makes all of them invisible, so the best way to create a legend here, would be to create a new axes to put the legend in. For that we need the handles of the plotted lines, so we need to change the ComplexRadar.plot() method to return the line. We then need to collect those lines in a list (call it lax) and supply them to the new legend.

I did make some changes to the code to make it work in python 2.7 and also to make it more comprehensible to myself. Here is, what should work:

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns 
import pandas as pd

def _scale_data(data, ranges):
    """scales data[1:] to ranges[0],
    """
    for d, (y1, y2) in zip(data[1:], ranges[1:]):
        assert (y1 <= d <= y2) or (y2 <= d <= y1)
    x1, x2 = ranges[0]
    d = data[0]
    sdata = [d]
    for d, (y1, y2) in zip(data[1:], ranges[1:]):
        sdata.append((d-y1) / (y2-y1) 
                     * (x2 - x1) + x1)
    return sdata

class ComplexRadar():
    def __init__(self, fig, variables, ranges,
                 n_ordinate_levels=6):
        angles = np.arange(0, 360, 360./len(variables))

        axes = [fig.add_axes([0.1,0.1,0.9,0.9],polar=True,
                label = "axes{}".format(i)) 
                for i in range(len(variables))]
        l, text = axes[0].set_thetagrids(angles, 
                                         labels=variables)
        [txt.set_rotation(angle-90) for txt, angle 
             in zip(text, angles)]
        for ax in axes[1:]:
            ax.patch.set_visible(False)
            ax.grid("off")
            ax.xaxis.set_visible(False)
        for i, ax in enumerate(axes):
            grid = np.linspace(*ranges[i], 
                               num=n_ordinate_levels)
            gridlabel = ["{}".format(round(x,2)) 
                         for x in grid]
            if ranges[i][0] > ranges[i][1]:
                grid = grid[::-1] # hack to invert grid
                          # gridlabels aren't reversed
            gridlabel[0] = "" # clean up origin
            ax.set_rgrids(grid, labels=gridlabel,
                         angle=angles[i])
            #ax.spines["polar"].set_visible(False)
            ax.set_ylim(*ranges[i])
        # variables for plotting
        self.angle = np.deg2rad(np.r_[angles, angles[0]])
        self.ranges = ranges
        self.ax = axes[0]

    def plot(self, data, *args, **kw):
        sdata = _scale_data(data, self.ranges)
        l = self.ax.plot(self.angle, np.r_[sdata, sdata[0]], *args, **kw)
        return l

    def fill(self, data, *args, **kw):
        sdata = _scale_data(data, self.ranges)
        self.ax.fill(self.angle, np.r_[sdata, sdata[0]], *args, **kw)



index = ["Carl","Michael","Peter","Louis","Sarah", "Laura","Nicholas"]      
df = pd.DataFrame({
    "Spe": pd.Series([89, 83, 70, 60, 30, 49, 28]),
    "Str": pd.Series([69, 53, 30, 20, 10, 29, 48]),
    "Det": pd.Series([82, 44, 79, 39, 20, 10, 85]),
    "Extr": pd.Series([59, 74, 29, 36, 18, 29, 18]),
    "Int": pd.Series([63, 11, 20, 36, 97, 58, 91]),
    "Est": pd.Series([12, 69, 89, 59, 19, 58, 98]),
    "Ape": pd.Series([29, 13, 94, 30, 20, 10, 67]),
})

variables = [k[0] for k in df.iteritems()]

ranges = [(1.,100.),(1.,100.),(1.,100.),(1.,100.),(1.,100.),(1.,100.),(1.,100.)] 
fig1 = plt.figure(figsize=(8, 8))
radar = ComplexRadar(fig1, variables, ranges)
lax = []
for i, name  in enumerate(index):
    data=df.iloc[i].values
    l, = radar.plot(data, label=name)
    lax.append(l)
    radar.fill(data,alpha=0.2)

legendax = fig1.add_axes([0.8,0.8,0.1,.1])
legendax.legend(handles = lax, labels=index, loc=3, bbox_to_anchor=(0,0,1,1), bbox_transform=fig1.transFigure )

legendax.axis('off')
sns.plt.show()

producing the following plot



来源:https://stackoverflow.com/questions/40208035/adding-legend-to-a-radarchart-in-python

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!