Radar chart with multiple scales on multiple axes
Asked Answered
M

3

17

I want to plot a radar chart with multiple scales on multiple axes using matplotlib. The official API example gives only one scale on one axis. (Scales are 0.2,0.4,0.6,0.8 in this example)

I want different scales on all axes. (There are 9 axes in the given example.)

I found an example of what I am looking for here. There are 5 axes on this example and 5 scales on all axes just like I want.

Malachi answered 9/7, 2014 at 16:24 Comment(0)
O
20

I think you can plot this with multiple axes, the lines are in the first axe, and other axes only shows ticklabels.

import numpy as np
import pylab as pl

class Radar(object):

    def __init__(self, fig, titles, labels, rect=None):
        if rect is None:
            rect = [0.05, 0.05, 0.95, 0.95]

        self.n = len(titles)
        self.angles = np.arange(90, 90+360, 360.0/self.n)
        self.axes = [fig.add_axes(rect, projection="polar", label="axes%d" % i) 
                         for i in range(self.n)]

        self.ax = self.axes[0]
        self.ax.set_thetagrids(self.angles, labels=titles, fontsize=14)

        for ax in self.axes[1:]:
            ax.patch.set_visible(False)
            ax.grid("off")
            ax.xaxis.set_visible(False)

        for ax, angle, label in zip(self.axes, self.angles, labels):
            ax.set_rgrids(range(1, 6), angle=angle, labels=label)
            ax.spines["polar"].set_visible(False)
            ax.set_ylim(0, 5)

    def plot(self, values, *args, **kw):
        angle = np.deg2rad(np.r_[self.angles, self.angles[0]])
        values = np.r_[values, values[0]]
        self.ax.plot(angle, values, *args, **kw)



fig = pl.figure(figsize=(6, 6))

titles = list("ABCDE")

labels = [
    list("abcde"), list("12345"), list("uvwxy"), 
    ["one", "two", "three", "four", "five"],
    list("jklmn")
]

radar = Radar(fig, titles, labels)
radar.plot([1, 3, 2, 5, 4],  "-", lw=2, color="b", alpha=0.4, label="first")
radar.plot([2.3, 2, 3, 3, 2],"-", lw=2, color="r", alpha=0.4, label="second")
radar.plot([3, 4, 3, 4, 2], "-", lw=2, color="g", alpha=0.4, label="third")
radar.ax.legend()

enter image description here

Offal answered 10/7, 2014 at 6:5 Comment(6)
I am getting this error - "global name 'angles' is not defined". Could you please add what are the changes you did.Knapsack
@Bharathi, bug fixed.Offal
Perhaps it's useful to note that if you want to see some output of the code above you have to add at least fig.savefig("somename.png") at the end.Jesselton
Also I had a problem using the example above with degrees greater than 360 for axis. I.e. the dashed line for the title "A" was missing. I solved this issue by adding a new line with "self.angles = [a % 360 for a in self.angles]" after "self.angles = np.arange(90, 90+360, 360.0/self.n)". Environment: Python 2.7.12 :: Anaconda 4.2.0 (64-bit), matplotlib (1.5.3), numpy (1.11.1). Anyway thanks for your work ;-)Jesselton
Had the same problem as @keocra, probably the libraries got updated and now behave differently. Here's my working solution for a 12-axis radar chart: gist.github.com/geberl/c65517bf8273552486f9a8954e80ddf4Shakeup
Yeah, the original solution no longer works, but Günther Eberl's is good.Abidjan
L
3

This accepts a dataframe of ints/floats and one id-column and dynamically generates the radar chart. Works irrespective of the range of each column because all values are internally scaled to be between [0,1] (techincally 0 and 1/1.25 since I wanted to give some padding), and simply prints a text of the actual value at the right location.

import pandas as pd
import numpy as np
from matplotlib import pyplot as plt

def spider(df, *, id_column, title=None, max_values=None, padding=1.25):
    categories = df._get_numeric_data().columns.tolist()
    data = df[categories].to_dict(orient='list')
    ids = df[id_column].tolist()
    if max_values is None:
        max_values = {key: padding*max(value) for key, value in data.items()}
        
    normalized_data = {key: np.array(value) / max_values[key] for key, value in data.items()}
    num_vars = len(data.keys())
    tiks = list(data.keys())
    tiks += tiks[:1]
    angles = np.linspace(0, 2 * np.pi, num_vars, endpoint=False).tolist() + [0]
    fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
    for i, model_name in enumerate(ids):
        values = [normalized_data[key][i] for key in data.keys()]
        actual_values = [data[key][i] for key in data.keys()]
        values += values[:1]  # Close the plot for a better look
        ax.plot(angles, values, label=model_name)
        ax.fill(angles, values, alpha=0.15)
        for _x, _y, t in zip(angles, values, actual_values):
            t = f'{t:.2f}' if isinstance(t, float) else str(t)
            ax.text(_x, _y, t, size='xx-small')
            
    ax.fill(angles, np.ones(num_vars + 1), alpha=0.05)
    ax.set_yticklabels([])
    ax.set_xticks(angles)
    ax.set_xticklabels(tiks)
    ax.legend(loc='upper right', bbox_to_anchor=(0.1, 0.1))
    if title is not None: plt.suptitle(title)
    plt.show()
    
radar = spider

spider(
    pd.DataFrame({
        'x': [*'abcde'],
        'c1': [10,11,12,13,14],
        'c2': [0.1, 0.3, 0.4, 0.1, 0.9],
        'c3': [1e5, 2e5, 3.5e5, 8e4, 5e4],
        'c4': [9, 12, 5, 2, 0.2],
        'test': [1,1,1,1,5]
    }),
    id_column='x',
    title='Sample Spider',
    padding=1.1
)

enter image description here

Lurette answered 7/3 at 15:11 Comment(1)
Implemented Mike's suggestion to simplify the process of identifying numeric columns.Lurette
I
1

Answer by @Yesh is good, but needs updating - I found that replacing the line:

categories = df.dtypes[(df.dtypes == 'float') | (df.dtypes == 'int')].index.tolist()

with:

categories = df._get_numeric_data().columns.tolist()

is a workaround that addresses the failure to identify int64 dtypes (etc) using df.dtypes == 'int'.

Ibbetson answered 8/5 at 21:48 Comment(1)
I'll incorporate it!Lurette

© 2022 - 2024 — McMap. All rights reserved.