今回は、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()
サンプル
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
なんだか色がドギツイですね。。
ちょっと改良・・
デフォルトの円グラフだと色がビビッドすぎたりするので、少し改良したくなります。
改良ポイントとしては、
- 色を優しく
- 上位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