linuxBean14.04(82)mpld3でツールチップ(やり直し編)

2015-10-23

旧ブログ

t f B! P L

前の関連記事:linuxBean14.04(81)mpld3で目盛りラベルを変更する


目盛りラベルの変更方法がわかったのでlinuxBean14.04(80)mpld3でツールチップをやり直します。

MySQLからpandasのデータフレームにデータを取得する

なぜかこの記事のコードを全部一つのセルにいれるとOutセルがでてきませんでした。

一つあたりのセルの入力容量の制限にでもひっかかるのでしょうかね。
In [1]:
#!~/anaconda3/bin/python3.4
# -*- coding: utf-8 -*-
import mysql.connector
from mysql.connector import errorcode
import pandas as pd
import matplotlib.pyplot as plt
import mpld3
from mpld3 import plugins
import numpy as np
config = {
    'user': 'User01',
    'password': 'user01_pass',
    'database': 'world'
}
try:
    cnx = mysql.connector.connect(**config)  # MySQLサーバに接続する。
except mysql.connector.Error as err:
    if err.errno == errorcode.ER_ACCESS_DENIED_ERROR:
        print("ユーザー名かパスワードが間違っています。")
    elif err.errno == errorcode.ER_BAD_DB_ERROR:
        print("データベース「{}」が存在しません。".format(config['database']))
    else:
        print(err)
else:
    query = 'SELECT Name, Continent, Population, LifeExpectancy, GNP FROM Country'  # MySQLに渡すSQL文。
    df = pd.read_sql(query,cnx) # pandasのデータフレームにテーブルを取得。
    cnx.close()  # MySQLとの接続を閉じる。

データフレームに得たデータを加工する

データフレームのインデックスを変更しているのはツールチップにインデックス番号に変わって国名を表示させたいからです。
In [2]:
    df.rename(columns={'Name': '国', 'Continent': '大陸', 'Population': '人口', 'LifeExpectancy': '平均余命', 'GNP':'国民総生産'},inplace=True) # 列インデックス名を変更。
    df.dropna(inplace=True)  # 欠損値がある行を削除。
    df = df[df['国民総生産']>100]  # 国民総生産が100より大きい行のみ取得。(logグラフに表示しやすいため)
    df = df.set_index(['国'])  # ラベルの列名を国にしたいので国をインデックスに変更する。
linuxBean14.04(80)mpld3でツールチップではdf.ix[df['国民総生産']>100]としましたが、df[df['国民総生産']>100]と結果が変わらずこれらの違いがわかりませんでした。
書式を設定するとデータ型が数値から文字列型に変わってしまってあとでプロットのx軸の値やマーカーサイズに使えなくなります。

そこで先にプロットに使う列だけ取り出しておきます。
In [3]:
df2=df[['人口','国民総生産']]
人口と国民総生産の列をカンマ区切り書式にします。
In [4]:
df[['人口','国民総生産']]=df[['人口','国民総生産']].applymap(lambda x: '{0:,.0f}'.format(x))
Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理のp135にはデータフレームのインデックス参照は「参照」であり「コピー」ではないので「参照」に対してデータを変更すると元のデータフレームに反映されるとあるので、書式の変更も反映されるのかと思いましたが反映されませんでしたのでdfのインデックス参照に書式設定したものを再代入しています。

PointHTMLTooltipで表示させるデータを作成する

ツールチップの書式はCSSで指定します。
In [5]:
    # ラベルのCSSの設定
    css = """
    table
    {
      border-collapse: collapse;
    }
    th
    {
      color: #ffffff;
      background-color: #000000;
    }
    td
    {
      background-color: #cccccc;
    }
    table, th, td
    {
      font-family:Arial, Helvetica, sans-serif;
      border: 1px solid black;
      text-align: right;
    }
    """
ツールチップに表示するデータを散布図に使うデータと同じ順序でリストに取得します。

データフレームの行を取得してそれを転置したものをリストに取得すればよいわけですが、方法としては4つ思いつきました。

%%timeitで測定してみると方法1より方法4につれて実行速度は短くなりましたがそう大差はありませんでした。(1ループで0.1秒も違うと大差あり?)
方法1: for文を使う方法(1 loops, best of 3: 718 ms per loop)
In [ ]:
    labels = []  # マーカーのラベルを入れるリスト。
    for i in df.index:  # iには国が入る。
        labels.append(df.ix[[i],:].T.to_html())  # データフレームをhtmlに変換してリストに取得。
方法2: for文でリストを作成しているのでリストの内包表記にできます。(1 loops, best of 3: 690 ms per loop)
In [ ]:
    labels=[df.ix[[i],:].T.to_html() for i in df.index]
方法3: データフレームをスライスしてから転置するよりも転置してからスライスしたほうが効率がよいように思うので、そうしてみます。(1 loops, best of 3: 679 ms per loop)
In [ ]:
    labels=[df.T.ix[:,[i]].to_html() for i in df.index]
方法4: for文を使うよりもpandasのメソッドを駆使した方が効率がよさそうなので、データフレームまず転置してapplyでシリーズとして得られる各列に対してデータフレームに変換したあとhtmlに変換してそのシリーズをリストに変換しました。(1 loops, best of 3: 612 ms per loop)
In [6]:
    labels=df.T.apply(lambda x:x.to_frame().to_html()).tolist()

x軸の目盛りラベルを変更するプラグインを作成する

$$${10}^{5}$$$という表示方法がわからなかったので漢数字にしました。
In [7]:
class RedrawXTickValue(mpld3.plugins.PluginBase): 
    """RedrawXTickValue plugin"""
    JAVASCRIPT = """
    mpld3.register_plugin("redrawxtickvalue", RedrawXTickValue);
    RedrawXTickValue.prototype = Object.create(mpld3.Plugin.prototype);
    RedrawXTickValue.prototype.constructor = RedrawXTickValue;
    function RedrawXTickValue(fig, props){mpld3.Plugin.call(this, fig, props);};
    RedrawXTickValue.prototype.draw = function(){
        var ax = this.fig.axes[0].elements[0];  // x軸を指定
        ax.axis.tickValues([2,3,4,5,6,7]);  // 目盛りラベルを表示させる位置を指定。
        ax.axis.tickFormat(function(x){        
            var tickfmt = ['百','千','万','十万','百万','千万'];  //目盛りラベル一覧の配列。
            return tickfmt[x-2];  //デフォルトの目盛りの値が目盛りラベル一覧の配列の要素番号と一致するようにします。
        });  // 目盛りラベルの書式設定
        this.fig.reset(); 
    }
    """
    def __init__(self):
        self.dict_ = {"type": "redrawxtickvalue"}

プラグインを使って散布図を描画する

ようやく散布図のプロットです。
使用するプラグインはplugins.connect()の2番目以降の引数に羅列します。
In [8]:
    fig, ax = plt.subplots(figsize=(8,6))  # 8x6インチの図に設定。
    ax.grid(True)  # グリッドの表示を有効にする。
    ax.set_xlim((np.log10(100), np.log10(20000000)))  # x軸の目盛りを100から20000000に制限する。
    ax.set_xlabel('国民総生産',size=15)  # x軸のラベルを設定。
    ax.set_ylabel('平均余命(年)',size=15)  # y軸のラベルを設定。
    ax.set_title('平均余命 対 国民総生産', size=20)  # プロットのタイトルを設定。
    ax.tick_params(labelsize=15)  # 目盛りラベルのフォントサイズを設定。
    points = ax.scatter(x=np.log10(df2['国民総生産']), y=df['平均余命'], s=df2['人口']/300000, alpha=0.5)  # 散布図をプロットする。
    tooltip = plugins.PointHTMLTooltip(points, labels, voffset=10, hoffset=10, css=css)
    plugins.connect(fig, tooltip, RedrawXTickValue())
    mpld3.display()
/home/pq/anaconda3/lib/python3.4/site-packages/matplotlib/collections.py:590: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
  if self._edgecolors == str('face'):
Out[8]:

国民総生産の単位はMySQL :: Other MySQL Documentationをみてもわかりませんでした。
百万米ドル?


マウスカーソルをマーカーに乗せるとツールチップが表示されます。

参考にしたサイト


python - How to generate a list from a pandas Data Frame with the column name and column values? - Stack Overflow
データフレームのitertuples()メソッドを使ってfor文に代わってリストを作成しようと思いましたが結局使いませんでした。

Built-in magic commands — IPython 3.2.1 documentation
Jupyter Notebookでは%%timeitで簡単にセルの実行速度を測定できます。

次の関連記事:linuxBean14.04(83)LibreOfiice5.0.2のインストール

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ