2016-12-30 4 views
13

Что-то вроде этого: enter image description hereКак сделать вафельные диаграммы в python? (Квадрат PieChart)

Существует очень хороший пакет to do it in R. В питона, самое лучшее, что я мог бы выяснить это, используя squarify пакет (вдохновленный a post on how to do treemaps):

import numpy as np 
import pandas as pd 
import matplotlib as mpl 
import matplotlib.pyplot as plt 
import seaborn as sns # just to have better line color and width 
import squarify 
# for those using jupyter notebooks 
%matplotlib inline 


df = pd.DataFrame({ 
        'v1': np.ones(100), 
        'v2': np.random.randint(1, 4, 100)}) 
df.sort_values(by='v2', inplace=True) 

# color scale 
cmap = mpl.cm.Accent 
mini, maxi = df['v2'].min(), df['v2'].max() 
norm = mpl.colors.Normalize(vmin=mini, vmax=maxi) 
colors = [cmap(norm(value)) for value in df['v2']] 

# figure 
fig = plt.figure() 
ax = fig.add_subplot(111, aspect="equal") 
ax = squarify.plot(df['v1'], color=colors, ax=ax) 
ax.set_xticks([]) 
ax.set_yticks([]); 

waffle

Но когда я создаю не 100, а 200 элементов (или другой неквадрат чисел), квадраты становятся несогласованными.

enter image description here

Другая проблема заключается в том, что если я изменю v2 до некоторой категориальной переменной (например, сто As, Bs, Cs и Ds), я получаю эту ошибку:

could not convert string to float: 'a'

Таким образом, может кто поможет мне с этими двумя вопросами:

  • Как я могу решить проблему выравнивания с не квадратными числами наблюдений?
  • Как использовать категориальные переменные в v2?

Помимо этого, я действительно открыт, если есть какие-либо другие пакеты python, которые могут более эффективно создавать участки вафель.

+1

[Здесь] (http://bokeh.pydata.org/en/latest/docs/gallery/unemployment.html) является примером, использующим 'bokeh' ... Вам нужно немного настроить его, чтобы получить пропорциональный вид, но да, это можно сделать в Python. – blacksite

+0

Спасибо @not_a_robot, я попробую боке на этой неделе. – lincolnfrias

+1

200 не является квадратным числом –

ответ

7

Я провел несколько дней, чтобы построить более общее решение, PyWaffle.

Вы можете установить его через

pip install pywaffle 

Исходный код: https://github.com/ligyxy/PyWaffle

PyWaffle не использует метод matshow(), но строит тех клетках, один за другим. Это облегчает настройку. Кроме того, то, что он предоставляет, представляет собой собственный класс фигуры, который возвращает объект фигуры. Обновляя атрибуты фигуры, вы можете в основном контролировать все на диаграмме.

Некоторые примеры:

Цветные или прозрачный фон:

import matplotlib.pyplot as plt 
from pywaffle import Waffle 

data = {'Democratic': 48, 'Republican': 46, 'Libertarian': 3} 
fig = plt.figure(
    FigureClass=Waffle, 
    rows=5, 
    values=data, 
    colors=("#983D3D", "#232066", "#DCB732"), 
    title={'label': 'Vote Percentage in 2016 US Presidential Election', 'loc': 'left'}, 
    labels=["{0} ({1}%)".format(k, v) for k, v in data.items()], 
    legend={'loc': 'lower left', 'bbox_to_anchor': (0, -0.4), 'ncol': len(data), 'framealpha': 0} 
) 
fig.gca().set_facecolor('#EEEEEE') 
fig.set_facecolor('#EEEEEE') 
plt.show() 

enter image description here

Используйте иконки замещающие квадраты:

data = {'Democratic': 48, 'Republican': 46, 'Libertarian': 3} 
fig = plt.figure(
    FigureClass=Waffle, 
    rows=5, 
    values=data, 
    colors=("#232066", "#983D3D", "#DCB732"), 
    legend={'loc': 'upper left', 'bbox_to_anchor': (1, 1)}, 
    icons='child', icon_size=18, 
    icon_legend=True 
) 

enter image description here

Несколько сюжетных линий в одном графике:

import pandas as pd 
data = pd.DataFrame(
    { 
     'labels': ['Hillary Clinton', 'Donald Trump', 'Others'], 
     'Virginia': [1981473, 1769443, 233715], 
     'Maryland': [1677928, 943169, 160349], 
     'West Virginia': [188794, 489371, 36258], 
    }, 
).set_index('labels') 

fig = plt.figure(
    FigureClass=Waffle, 
    plots={ 
     '311': { 
      'values': data['Virginia']/30000, 
      'labels': ["{0} ({1})".format(n, v) for n, v in data['Virginia'].items()], 
      'legend': {'loc': 'upper left', 'bbox_to_anchor': (1.05, 1), 'fontsize': 8}, 
      'title': {'label': '2016 Virginia Presidential Election Results', 'loc': 'left'} 
     }, 
     '312': { 
      'values': data['Maryland']/30000, 
      'labels': ["{0} ({1})".format(n, v) for n, v in data['Maryland'].items()], 
      'legend': {'loc': 'upper left', 'bbox_to_anchor': (1.2, 1), 'fontsize': 8}, 
      'title': {'label': '2016 Maryland Presidential Election Results', 'loc': 'left'} 
     }, 
     '313': { 
      'values': data['West Virginia']/30000, 
      'labels': ["{0} ({1})".format(n, v) for n, v in data['West Virginia'].items()], 
      'legend': {'loc': 'upper left', 'bbox_to_anchor': (1.3, 1), 'fontsize': 8}, 
      'title': {'label': '2016 West Virginia Presidential Election Results', 'loc': 'left'} 
     }, 
    }, 
    rows=5, 
    colors=("#2196f3", "#ff5252", "#999999"), # Default argument values for subplots 
    figsize=(9, 5) # figsize is a parameter of plt.figure 
) 

enter image description here

8

Я собрал рабочий пример ниже, который, я думаю, соответствует вашим потребностям. Некоторая работа необходима для полного обобщения подхода, но я думаю, вы обнаружите, что это хорошее начало. Хитрость заключалась в том, чтобы использовать matshow(), чтобы решить вашу проблему без квадрата, и создать пользовательскую легенду, чтобы легко учитывать категориальные значения.

import numpy as np 
import pandas as pd 
import matplotlib as mpl 
import matplotlib.pyplot as plt 
import matplotlib.patches as mpatches 

# Let's make a default data frame with catagories and values. 
df = pd.DataFrame({ 'catagories': ['cat1', 'cat2', 'cat3', 'cat4'], 
        'values': [84911, 14414, 10062, 8565] }) 
# Now, we define a desired height and width. 
waffle_plot_width = 20 
waffle_plot_height = 7 

classes = df['catagories'] 
values = df['values'] 

def waffle_plot(classes, values, height, width, colormap): 

    # Compute the portion of the total assigned to each class. 
    class_portion = [float(v)/sum(values) for v in values] 

    # Compute the number of tiles for each catagories. 
    total_tiles = width * height 
    tiles_per_class = [round(p*total_tiles) for p in class_portion] 

    # Make a dummy matrix for use in plotting. 
    plot_matrix = np.zeros((height, width)) 

    # Popoulate the dummy matrix with integer values. 
    class_index = 0 
    tile_index = 0 

    # Iterate over each tile. 
    for col in range(waffle_plot_width): 
     for row in range(height): 
      tile_index += 1 

      # If the number of tiles populated is sufficient for this class... 
      if tile_index > sum(tiles_per_class[0:class_index]): 

       # ...increment to the next class. 
       class_index += 1  

      # Set the class value to an integer, which increases with class. 
      plot_matrix[row, col] = class_index 

    # Create a new figure. 
    fig = plt.figure() 

    # Using matshow solves your "non-square" problem. 
    plt.matshow(plot_matrix, cmap=colormap) 
    plt.colorbar() 

    # Get the axis. 
    ax = plt.gca() 

    # Minor ticks 
    ax.set_xticks(np.arange(-.5, (width), 1), minor=True); 
    ax.set_yticks(np.arange(-.5, (height), 1), minor=True); 

    # Gridlines based on minor ticks 
    ax.grid(which='minor', color='w', linestyle='-', linewidth=2) 

    # Manually constructing a legend solves your "catagorical" problem. 
    legend_handles = [] 
    for i, c in enumerate(classes): 
     lable_str = c + " (" + str(values[i]) + ")" 
     color_val = colormap(float(i+1)/len(classes)) 
     legend_handles.append(mpatches.Patch(color=color_val, label=lable_str)) 

    # Add the legend. Still a bit of work to do here, to perfect centering. 
    plt.legend(handles=legend_handles, loc=1, ncol=len(classes), 
       bbox_to_anchor=(0., -0.1, 0.95, .10)) 

    plt.xticks([]) 
    plt.yticks([]) 

# Call the plotting function. 
waffle_plot(classes, values, waffle_plot_height, waffle_plot_width, 
      plt.cm.coolwarm) 

Ниже приведен пример вывода этого сценария. Как вы можете видеть, это работает для меня хорошо, и отвечает всем вашим заявленным потребностям. Просто дайте мне знать, если это даст вам неприятности. Наслаждайтесь!

waffle_plot

+0

Да, это очень хороший подход! Надеюсь, вы сможете решить проблему легенды :) – lincolnfrias

+0

@lincolnfrias, я исправил и отредактировал ответ. Теперь он должен делать все, что вы ищете. –

+0

Большое спасибо за такой своевременный и отличный ответ, Джастин. Congrats! – lincolnfrias

Смежные вопросы