Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matplotlib: How to remove the vertical space when displaying circles on a grid?

I need to draw a punchcard with matplotlib which seem to not have such a function.So I have coded the following one:

import matplotlib.pyplot as plt
import numpy as np

def draw_punchcard(infos,
                ax1=range(7),
                ax2=range(24),
                ax1_ticks=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
                ax2_ticks=range(24),
                ax1_label='Day',
                ax2_label='Hour'):
    """Construct a punchcard.
    Quick'n dirty way.

    Parameters
    ==========
    - infos: Dictionary of quantities to display.
            They are indexed by key of type (val1,val2) with
            val1 included in ax1 and val2 included in ax2.
    - ax1: list
            Possible values for first axe (if different than days)
    - ax2: list
            Possible values for second axe (if different than hours)
    - ax1_ticks: list
            Value to display in ticks of first axe (if different than days)
    - ax2_ticks: list
            Value to display in ticks of second axe (if different than days)
    - ax1_label: String
            Value to give to first axis (if different than day)
    - ax2_label: String
            Value to give to second axis (if different than day)

    """

    # build the array which contains the values
    data = np.zeros((len(ax1),len(ax2)))
    for key in infos:
        data[key[0],key[1]] = infos[key]
    data = data/float(np.max(data))


    # Draw the punchcard (create one circle per element)
    # Ugly normalisation allows to obtain perfect circles instead of ovals....
    for y in range(data.shape[0]):
        for x in range(data.shape[1]):
            circle = plt.Circle((x/float(data.shape[1])*data.shape[0],y),
                                data[y][x]/float(data.shape[1])*data.shape[0]/2)
            plt.gca().add_artist(circle)

    plt.ylim(0-0.5, data.shape[0]-0.5)
    plt.xlim(0, data.shape[0])
    plt.yticks(np.arange(len(ax1)), ax1_ticks)
    plt.xticks(np.linspace(0,len(ax1),len(ax2))+0.5/float(data.shape[1]), ax2_ticks)
    plt.xlabel(ax1_label)
    plt.ylabel(ax2_label)
    plt.gca().invert_yaxis()

However it does not exactly work as expected. If it is evaluated on the following example:

infos = {(6, 9): 12196, (0, 20): 22490, (1, 17): 59636, (0, 7): 14915, (2, 22): 7193, (1, 6): 11694, (0, 10): 85793, (3, 7): 17507, (2, 5): 4078, (1, 11): 83424, (5, 8): 33625, (4, 0): 1915, (6, 7): 10528, (5, 5): 3525, (4, 19): 33253, (6, 10): 12186, (5, 18): 20856, (0, 17): 61370, (0, 4): 551, (1, 1): 389, (4, 10): 94684, (3, 2): 286, (2, 6): 11845, (5, 11): 46822, (4, 5): 5215, (3, 23): 1841, (6, 0): 3441, (4, 16): 94545, (6, 23): 1285, (5, 21): 11096, (2, 17): 59928, (0, 1): 279, (3, 12): 56193, (1, 12): 59846, (4, 15): 102986, (3, 1): 371, (2, 11): 78007, (5, 14): 27711, (3, 18): 41365, (6, 13): 11994, (4, 21): 14477, (6, 16): 11669, (1, 21): 13629, (2, 18): 42399, (0, 14): 66284, (3, 11): 76402, (2, 1): 358, (1, 15): 93381, (4, 12): 67279, (2, 12): 57427, (5, 1): 509, (3, 17): 58974, (6, 14): 11383, (0, 21): 12604, (1, 16): 86199, (2, 23): 1914, (1, 5): 4002, (0, 11): 79164, (3, 6): 11434, (2, 2): 304, (1, 10): 88874, (4, 1): 420, (6, 4): 750, (5, 4): 783, (6, 11): 12886, (5, 17): 21573, (0, 18): 41842, (1, 19): 33073, (0, 5): 2777, (1, 0): 1189, (0, 8): 46486, (4, 11): 89246, (3, 5): 4105, (2, 7): 18534, (5, 10): 54826, (4, 6): 14638, (3, 22): 5043, (6, 1): 894, (5, 7): 16052, (4, 17): 66899, (6, 20): 16085, (5, 20): 18041, (0, 2): 219, (3, 15): 81526, (1, 3): 251, (4, 8): 58008, (3, 0): 1581, (2, 8): 47233, (5, 13): 23896, (3, 21): 13998, (6, 2): 540, (4, 22): 5920, (6, 17): 13856, (5, 23): 2155, (1, 20): 24386, (2, 19): 33216, (0, 15): 86664, (3, 10): 81444, (1, 14): 74440, (4, 13): 62307, (2, 13): 51784, (5, 0): 1959, (3, 16): 76742, (6, 15): 11438, (0, 22): 4055, (6, 18): 17554, (1, 23): 1681, (2, 20): 26427, (1, 4): 710, (0, 12): 59008, (3, 9): 72555, (2, 3): 372, (1, 9): 79140, (4, 2): 322, (2, 14): 68869, (6, 5): 3091, (5, 3): 392, (6, 8): 11720, (5, 16): 28663, (0, 19): 30223, (1, 18): 41624, (0, 6): 8791, (1, 7): 18280, (0, 9): 75860, (3, 4): 765, (2, 4): 834, (5, 9): 52874, (4, 7): 21830, (6, 6): 7618, (5, 6): 9935, (4, 18): 43274, (6, 21): 9836, (5, 19): 20758, (0, 16): 81458, (0, 3): 245, (3, 14): 66845, (1, 2): 291, (4, 9): 86355, (3, 3): 346, (2, 9): 71401, (5, 12): 27939, (4, 4): 987, (3, 20): 24478, (6, 3): 450, (4, 23): 2236, (6, 22): 3779, (5, 22): 4950, (2, 16): 79009, (0, 0): 1655, (3, 13): 53589, (1, 13): 55308, (4, 14): 81394, (2, 10): 80932, (5, 15): 32751, (3, 19): 32193, (6, 12): 12770, (4, 20): 24379, (0, 23): 1240, (6, 19): 18908, (1, 22): 4887, (2, 21): 16508, (0, 13): 54858, (3, 8): 47367, (2, 0): 1778, (1, 8): 50393, (4, 3): 387, (2, 15): 86256, (5, 2): 385}
draw_punchcard(infos)
plt.show()

we obtain the following result:Generated punchard

You can notice there are a lot of empty vertical space between the circles, whereas I would expect two circles of maximum size to touch together (it would be the case horizontally, but not vertically).

So, how to remove this space, while keeping the circle ratio?

Thanks a lot for your help.

like image 437
rgiot Avatar asked Feb 13 '13 09:02

rgiot


People also ask

How do I get rid of grid lines in Matplotlib?

MatPlotLib with Python Load an image from a file. Convert the image from one color space to another. To remove grid lines, use ax. grid(False).

How do I reduce the space between subplots in Matplotlib?

Using subplots_adjust() method to set the spacing between subplots. We can use the plt. subplots_adjust() method to change the space between Matplotlib subplots. The parameters wspace and hspace specify the space reserved between Matplotlib subplots.


1 Answers

I changed the vertical spacing of the circles based on the shape ratio of the data (r = float(data.shape[1])/data.shape[0]). Also, the canvas size is changed to fit the result, so that you're not left with a large white area in the default canvas size.

Result:

Circle Plot with proper circle scaling

def draw_punchcard(infos,
                ax1=range(7),
                ax2=range(24),
                ax1_ticks=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
                ax2_ticks=range(24),
                ax1_label='Day',
                ax2_label='Hour'):

    # build the array which contains the values
    data = np.zeros((len(ax1),len(ax2)))
    for key in infos:
        data[key[0],key[1]] = infos[key]
    data = data/float(np.max(data))

    # shape ratio
    r = float(data.shape[1])/data.shape[0]

    # Draw the punchcard (create one circle per element)
    # Ugly normalisation allows to obtain perfect circles instead of ovals....
    for y in range(data.shape[0]):
        for x in range(data.shape[1]):
            circle = plt.Circle((x/float(data.shape[1]-1)*(data.shape[0]-1),y/r),
                                data[y][x]/float(data.shape[1])*data.shape[0]/2)
            plt.gca().add_artist(circle)

    plt.ylim(0-0.5,  data.shape[0]-0.5)
    plt.xlim(0, data.shape[0])
    plt.yticks(np.arange(0,len(ax1)/r-.1,1/r), ax1_ticks)
    xt = np.linspace(0, len(ax1)-1, len(ax2))
    plt.xticks(xt, ax2_ticks)
    plt.xlabel(ax1_label)
    plt.ylabel(ax2_label)
    plt.gca().invert_yaxis()

    # make sure the axes are equal, and resize the canvas to fit the plot
    plt.axis('equal')
    plt.axis([0, 7, 6/r, -.5])
    scale = 0.5
    plt.gcf().set_size_inches(data.shape[1]*scale,data.shape[0]*scale, forward=True)
like image 119
Junuxx Avatar answered Oct 07 '22 01:10

Junuxx