akiyoko blog

akiyoko の IT技術系ブログです

Pythonで円グラフ

今回は、matplotlibライブラリを使って、円グラフを描きたいと思います。

例として、参加者の国籍ごとの人数を表示する、という用途で使う前提で考えます。
Excelのあるセル(D10)から下方向に、参加者の国籍データが並んでいるものとします。

USA
Denmark
Japan
Denmark
Italy

 ・
 ・

円グラフ

円グラフ(パイチャート)を描くには、matplotlib.axes.Axesクラスの pie() を使います。ヒストグラムや散布図と比べるとパラメータはシンプルで、x を指定するだけでも動作させることができます。

pie(x, explode=None, labels=None,
    colors=('b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'),
    autopct=None, pctdistance=0.6, shadow=False,
    labeldistance=1.1, startangle=None, radius=None)

x: 円グラフ切片の度数(配列で指定)
explode: 切片のオフセット(タプルで指定。配列でもいいっぽい)
labels: 円グラフ切片のラベル(配列で指定)
colors: 切片につける色を順番に指定(タプルで指定。配列でもいいっぽい)
autopct: 度数の割合をパーセンテージで表示できる(フォーマットを指定)
shadow: 影をつけるかどうか
startangle: 基線の位置(デフォルトは右90度。90と指定すると真上から開始)

シンプルな円グラフはこんな感じです。

from matplotlib import pyplot as plt

if __name__ == '__main__':
    fig = plt.figure(figsize=(8, 8))
    ax = fig.add_subplot(111)
    ax.pie([4, 3, 2, 1])
    plt.show()

f:id:akiyoko:20130624003832p:plain

サンプル

test_pie_chart.py

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import collections
import numpy
import xlrd
from matplotlib import pyplot as plt


def get_data(sheet, rowx, colx):
    data = []
    for row in range(rowx, sheet.nrows):
        value = sheet.cell(row, colx).value
        if value != '':
            data.append(value)
    data = numpy.array(data)
    return data


if __name__ == '__main__':
    book = xlrd.open_workbook('/Users/akiyoko/Documents/temp/2nd_test.xls')
    sheet = book.sheet_by_name('Statistics (total score)')
    data = get_data(sheet, 9, 3)  # データの起点はD10
    print 'data=%s' % data
    print 'size of data=%d' % len(data)

    # 度数の大きい順に並べ替え
    # counter = [(x, data.count(x)) for x in set(data)] としたいところだが、
    # numpy.ndarray を使うとエラーになってしまう(countメソッドがないため)
    # そこで、collections.Counter を使ってみた(ただし、Python 2.7以上が必要)
    counter = collections.Counter(data)
    ranked_data = counter.most_common()  # sorted(counter.items(), key=lambda x: x[1], reverse=True) としてもOK
    print 'ranked_data=%s' % ranked_data
    # ラベル
    labels = [x[0] for x in ranked_data]  # list(zip(*ranked_data)[0]) としてもOK
    print 'labels=%s' % labels
    # 切片
    values = [x[1] for x in ranked_data]  # list(zip(*ranked_data)[1]) としてもOK
    print 'values=%s' % values
    print 'size of sectors=%s' % len(values)
    # 合計
    total = sum(values)
    print 'N=%d' % total

    # 共通初期設定
    plt.rc('font', **{'family': 'serif'})
    # キャンバス
    fig = plt.figure(figsize=(8, 8))
    # プロット領域(1x1分割の1番目に領域を配置せよという意味)
    ax = fig.add_subplot(111)
    # 円グラフ
    ax.pie(values, labels=labels)
    # タイトル
    ax.set_title('Pie Chart: N=%s' % total, size=16)
    plt.show()

実行結果

$ python test_pie_chart.py 
data=[u'USA' u'Denmark' u'Japan' u'Denmark' u'Italy' u'Russia' u'Turkey'
 u'Denmark' u'Greece' u'Japan' u'?' u'Germany' u'Japan' u'Turkey' u'UK'
 u'Japan' u'Denmark' u'Turkey' u'UK' u'Germany' u'?' u'Turkey' u'Norway'
 u'USA/Singapore' u'Sweden' u'Denmark' u'Japan' u'Hong Kong' u'Finland'
 u'Japan' u'Italy' u'Denmark' u'Belgium' u'Norway' u'Japan' u'Denmark'
 u'Norway' u'Turkey' u'Czech' u'Germany' u'Japan' u'Japan' u'Haiti'
 u'Japan' u'Denmark' u'UK' u'Germany' u'Turkey' u'Denmark' u'Japan'
 u'Germany' u'Japan' u'Czech' u'UK' u'Sweden' u'Switzerland' u'Turkey'
 u'Japan' u'UK' u'Japan' u'Sweden' u'Japan' u'Denmark' u'UK' u'Japan'
 u'Switzerland' u'Turkey' u'Norway' u'Sweden' u'Germany' u'France' u'Japan'
 u'UK' u'Japan' u'Japan' u'Switzerland' u'Czech' u'Japan' u'Japan'
 u'Denmark' u'Japan' u'Japan' u'Sweden' u'Denmark' u'Norway' u'Israel'
 u'Japan' u'Japan' u'Turkey' u'Turkey' u'Turkey' u'Turkey' u'Lebanon'
 u'Turkey' u'Japan' u'Turkey' u'Japan' u'Japan' u'Japan' u'Japan' u'Japan'
 u'Turkey' u'Spain' u'Belgium' u'Belgium']
size of data=105
ranked_data=[(u'Japan', 31), (u'Turkey', 15), (u'Denmark', 12), (u'UK', 7), (u'Germany', 6), (u'Norway', 5), (u'Sweden', 5), (u'Belgium', 3), (u'Switzerland', 3), (u'Czech', 3), (u'Italy', 2), (u'?', 2), (u'USA', 1), (u'France', 1), (u'Israel', 1), (u'Haiti', 1), (u'Hong Kong', 1), (u'USA/Singapore', 1), (u'Finland', 1), (u'Russia', 1), (u'Lebanon', 1), (u'Spain', 1), (u'Greece', 1)]
labels=[u'Japan', u'Turkey', u'Denmark', u'UK', u'Germany', u'Norway', u'Sweden', u'Belgium', u'Switzerland', u'Czech', u'Italy', u'?', u'USA', u'France', u'Israel', u'Haiti', u'Hong Kong', u'USA/Singapore', u'Finland', u'Russia', u'Lebanon', u'Spain', u'Greece']
values=[31, 15, 12, 7, 6, 5, 5, 3, 3, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
size of sectors=23
N=105

f:id:akiyoko:20130621234459p:plain

なんだか色がドギツイですね。。

ちょっと改良・・

デフォルトの円グラフだと色がビビッドすぎたりするので、少し改良したくなります。
改良ポイントとしては、

  • 色を優しく
  • 上位10コ分を表示。それ以外は Others
  • 強調したい切片をオフセット(切り離し)
  • 基線を真上から開始
  • パーセンテージを表示

な感じで。


test_improved_pie_chart.py

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import collections
import numpy
import random
import xlrd
from matplotlib import pyplot as plt


def get_data(sheet, rowx, colx):
    data = []
    for row in range(rowx, sheet.nrows):
        value = sheet.cell(row, colx).value
        if value != '':
            data.append(value)
    data = numpy.array(data)
    return data


if __name__ == '__main__':
    book = xlrd.open_workbook('/Users/akiyoko/Documents/temp/2nd_test.xls')
    sheet = book.sheet_by_name('Statistics (total score)')
    data = get_data(sheet, 9, 3)  # データの起点はD10
    print 'data=%s' % data
    print 'size of data=%d' % len(data)

    # 度数の大きい順に並べ替え
    counter = collections.Counter(data)
    # 上位10コ分を表示(それ以外は Others)
    ranked_data = counter.most_common(10)
    print 'ranked_data=%s' % ranked_data
    # ラベル
    labels = [x[0] for x in ranked_data] + ['Others']
    print 'labels=%s' % labels
    # 切片
    values = [x[1] for x in ranked_data]
    values = values + [len(data) - sum(values)]
    print 'values=%s' % values
    print 'size of sectors=%s' % len(values)
    # 合計
    total = sum(values)
    print 'N=%d' % total

    # 共通初期設定
    plt.rc('font', **{'family': 'serif'})
    # キャンバス
    fig = plt.figure(figsize=(8, 8))
    # プロット領域(1x1分割の1番目に領域を配置せよという意味)
    ax = fig.add_subplot(111)
    # 切片のオフセット
    # 例えば (0.05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) というタプルを作る
    explode = [0.05] + [0] * (len(values) - 1)
    # 切片の色(青〜紫でランダムに指定)
    colors = []
    for _ in range(len(values)):
        rgb = '#%02x%02x%02x' % (random.randrange(96, 160, 4),
                                 random.randrange(96, 160, 4),
                                 random.randrange(224, 256, 4))
        print 'rgb=%s' % rgb
        colors.append(rgb)
    # 円グラフ
    ax.pie(values, labels=labels, explode=tuple(explode), colors=tuple(colors),
           autopct='%.1f%%', shadow=True, startangle=90)
    # タイトル
    ax.set_title('Pie Chart: N=%s' % total, size=16)
    plt.show()

実行結果

$ python test_improved_pie_chart.py 
data=[u'USA' u'Denmark' u'Japan' u'Denmark' u'Italy' u'Russia' u'Turkey'
 u'Denmark' u'Greece' u'Japan' u'?' u'Germany' u'Japan' u'Turkey' u'UK'
 u'Japan' u'Denmark' u'Turkey' u'UK' u'Germany' u'?' u'Turkey' u'Norway'
 u'USA/Singapore' u'Sweden' u'Denmark' u'Japan' u'Hong Kong' u'Finland'
 u'Japan' u'Italy' u'Denmark' u'Belgium' u'Norway' u'Japan' u'Denmark'
 u'Norway' u'Turkey' u'Czech' u'Germany' u'Japan' u'Japan' u'Haiti'
 u'Japan' u'Denmark' u'UK' u'Germany' u'Turkey' u'Denmark' u'Japan'
 u'Germany' u'Japan' u'Czech' u'UK' u'Sweden' u'Switzerland' u'Turkey'
 u'Japan' u'UK' u'Japan' u'Sweden' u'Japan' u'Denmark' u'UK' u'Japan'
 u'Switzerland' u'Turkey' u'Norway' u'Sweden' u'Germany' u'France' u'Japan'
 u'UK' u'Japan' u'Japan' u'Switzerland' u'Czech' u'Japan' u'Japan'
 u'Denmark' u'Japan' u'Japan' u'Sweden' u'Denmark' u'Norway' u'Israel'
 u'Japan' u'Japan' u'Turkey' u'Turkey' u'Turkey' u'Turkey' u'Lebanon'
 u'Turkey' u'Japan' u'Turkey' u'Japan' u'Japan' u'Japan' u'Japan' u'Japan'
 u'Turkey' u'Spain' u'Belgium' u'Belgium']
size of data=105
ranked_data=[(u'Japan', 31), (u'Turkey', 15), (u'Denmark', 12), (u'UK', 7), (u'Germany', 6), (u'Norway', 5), (u'Sweden', 5), (u'Belgium', 3), (u'Switzerland', 3), (u'Czech', 3)]
labels=[u'Japan', u'Turkey', u'Denmark', u'UK', u'Germany', u'Norway', u'Sweden', u'Belgium', u'Switzerland', u'Czech', 'Others']
values=[31, 15, 12, 7, 6, 5, 5, 3, 3, 3, 15]
size of sectors=11
N=105
rgb=#709cf4
rgb=#949ce0
rgb=#947ce8
rgb=#8494f4
rgb=#8474fc
rgb=#9094e0
rgb=#848ce4
rgb=#9880f4
rgb=#7898f8
rgb=#8090f4
rgb=#9c94fc

f:id:akiyoko:20130625224424p:plain