Search code examples
pythonpython-2.7matplotlibdata-visualizationradar-chart

"Radial grids must be strictly positive" error on radar chart


Whilst plotting my complex radar chart, I am getting the error "radial grids must be strictly positive", but all of the values and ranges I have used in my radar chart are all positive. Can someone tell me why I am getting this error, and what I can do to fix it? The code I have used is below:-

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


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.8,0.8],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)

# example data
variables = ("Attribute1", "Attribute2", "Attribute3", 
            "Attribute4", "Attribute5", "Attribute6", "Attribute7",
             "Attribute8", "Attribute9", "Attribute10", "Attribute11",
             "Attribute12", "Attribute13", "Attribute14", "Attribute15",
             "Attribute16", "Attribute17")
data = (1.0, 185, 0.02, 
        1.5, 0.1, 1.3, 1.1,
        2.3, 0.1, 6.6, 24.5,
        83, 7.5, 5.6, 4.6,
        6.5, 1.3)
data2 = (1.0, 177, 0.0,
         0.0, 0.7, 1.3, 1.3,
         4.3, 0.0, 4.7, 30,
         93, 3.7, 5.0, 7.7,
         8.0, 3.5)
ranges = [(10.0, 0.0), (170.0, 189.0), (0.0, 0.3),
         (0.6, 5.2), (0.1, 2.0), (0.3, 2.2), (1.4, 0.3),
         (0.9, 3.0), (0.0, 0.7), (2.6, 11.4), (14.4, 43.5),
         (72.0, 94.0), (1.4, 7.9), (2.5, 6.5), (2.4, 6.2),
         (4.0, 7.9), (1.1, 3.9)]            
# plotting
fig1 = plt.figure(figsize=(9, 9))
radar = ComplexRadar(fig1, variables, ranges)
radar.plot(data, color="deepskyblue", label= "Compare1")
radar.fill(data, color="deepskyblue", alpha=0.5)
radar = ComplexRadar(fig1, variables, ranges)
radar.plot(data2, color="orangered", label= "Compare2")
radar.fill(data2, color="orangered", alpha=0.5)
radar.ax.legend(loc='upper center', bbox_to_anchor=(0.9, 1.10),
      fancybox=False, shadow=False, ncol=48)
plt.show()   

Solution

  • Hi this is great time to learn some debugging skills. The first thing you'll want to do when confronted with an error like this is localize it in your code. Fortunately python does this for you with the traceback. These tracebacks can at times be super intimidating but on closer inspection start to make a little more sense and can provide very valuable information. So lets start with the traceback for the error.

    The first two sections of the traceback are:

    ValueError                                Traceback (most recent call last)
    <ipython-input-454-63a43ee81b1a> in <module>()
         91 # plotting
         92 fig1 = plt.figure(figsize=(9, 9))
    ---> 93 radar = ComplexRadar(fig1, variables, ranges)
         94 radar.plot(data, color="deepskyblue", label= "Compare1")
         95 radar.fill(data, color="deepskyblue", alpha=0.5)
    
    <ipython-input-454-63a43ee81b1a> in __init__(self, fig, variables, ranges, n_ordinate_levels)
         54             gridlabel[0] = "" # clean up origin
         55             ax.set_rgrids(grid, labels=gridlabel,
    ---> 56                          angle=angles[i])
         57             #ax.spines["polar"].set_visible(False)
         58             ax.set_ylim(*ranges[i])
    

    So we can see that the issue is coming from ax.set_rgrids in the ComplexRadar __init__ method. So we know its going to be a problem with the arguments we are passing that, either grid or angles. To get more of an idea lets look at the final section of the error message:

    /home/ianhi/anaconda/lib/python2.7/site-packages/matplotlib/projections/polar.pyc in set_rgrids(self, radii, labels, angle, fmt, **kwargs)
        565         rmin = radii.min()
        566         if rmin &lt;= 0:
    --> 567             raise ValueError('radial grids must be strictly positive')
        568 
        569         self.set_yticks(radii)
    
    ValueError: radial grids must be strictly positive
    

    if rmin <= 0: So there is where we get "strictly positive" from. While none of the radial values you are passing are negative some of them may not be greater than 0. For short code like this an easy way to investigate is to simply add a print statement before the offending line.

    print(grid)
    ax.set_rgrids(grid, labels=gridlabel,
                 angle=angles[i])
    

    Doing this I find that grid = [ 0. 2. 4. 6. 8. 10.]. AhHah! This contains a 0. You'll have to choose how you where you want to start grid from.

    For testing purpose I used the quick and dirty fix of grid[0]=.01 only to find that the second radar.plot command also throws an error!

    This gives us a rather mysterious blank AssertionError:

    AssertionError                            Traceback (most recent call last)
    <ipython-input-458-2a30b515f0d9> in <module>()
         96 radar.fill(data, color="deepskyblue", alpha=0.5)
         97 radar = ComplexRadar(fig1, variables, ranges)
    ---> 98 radar.plot(data2, color="orangered", label= "Compare2")
         99 radar.fill(data2, color="orangered", alpha=0.5)
        100 radar.ax.legend(loc='upper center', bbox_to_anchor=(0.9, 1.10),
    
    <ipython-input-458-2a30b515f0d9> in plot(self, data, *args, **kw)
         63         self.ax = axes[0]
         64     def plot(self, data, *args, **kw):
    ---> 65         sdata = _scale_data(data, self.ranges)
         66         self.ax.plot(self.angle, np.r_[sdata, sdata[0]], *args, **kw)
         67     def fill(self, data, *args, **kw):
    
    &lt;ipython-input-458-2a30b515f0d9> in _scale_data(data, ranges)
         13     inverts if the scale is reversed"""
         14     for d, (y1, y2) in zip(data[1:], ranges[1:]):
    ---> 15         assert (y1 <= d <= y2) or (y2 <= d <= y1)
         16     x1, x2 = ranges[0]
         17     d = data[0]
    
    AssertionError: 
    

    The reason there is no helpful message describing the problem is that it is your method _scale_data that is actually raising this error. This assert statement is telling us something about the value of d relative to y2 and y1. Rather, its actually telling us that some thing we think about d is untrue. To figure out what is going on lets surround the assert statement in a try except block:

    try:
        assert (y1 <= d <= y2) or (y2 <= d <= y1)
    except AssertionError:
        print(d,y1,y2)
    

    Leaving us with several examples where d satisfies neither of the or conditions:

     d     y1   y2
    
    (0.0, 0.6, 5.2)
    (4.3, 0.9, 3.0)
    (7.7, 2.4, 6.2)
    (8.0, 4.0, 7.9)
    (0.0, 0.6, 5.2)
    (4.3, 0.9, 3.0)
    (7.7, 2.4, 6.2)
    (8.0, 4.0, 7.9)
    

    This try except statement along with grid[0]=.001 right before ax.set_rgrids command allows your code to run giving the below plot. However, you should look into changing your data set to avoid the grid[0]=0 issue and possibly remove the assert statement or else correct the data if it fails. Hope this helps and you have an easier time debugging next time because of this.

    enter image description here