はじめに
各データの時系列変化を順位とともに表現するプロット、Bar Chart Race。前回は barh
関数を使って Bar Char Race が実現できそうか確認してみました。BarWidth
プロパティの使い方が肝でしたね。
詳細:ぬめぬめ動く棒グラフ Bar Chart Race を描いてみよう: 準備編
今回は実装編として実際にこの動画(GIF) を作成するところまでを解説します。
既に関数を minoue-xx/BarChartRaceAnimation で公開していますので、使用例とポイントだけに絞ってご紹介します。
実行環境
R2019b で作りました。下で解説する Arguments に関わる部分を取り除けば R2017b でも動くと思います。試してないです。
使用例:各都道府県の推定人口推移(大正9年~平成12年)
まず e-Stat のページから該当データをダウンロードして、05k5-5.xlsx
というファイルが本スクリプトと同じフォルダにダウンロードされたと仮定します。
Step 1: データ読み込み
ザクっとインポートツールから読み込むスクリプトを作りました。
「現在のフォルダ」に表示される 05k5-5.xlsx
をダブルクリックして、読み込み範囲を指定して「スクリプトの生成」をクリック。省エネ。出力タイプは:cell 配列。
生成されたスクリプトを実行すると変数 k55
として読み込まれるはず。(importData.m はこちらにもおいています。)
importData
Step 2: データ整理
時系列データは timetable
型でまとめると便利。注:沖縄はデータが大きく欠けているところがあるので除いています。
% k55 から必要な部分を取り出します。
years = [k55{1,3:end}]'; % 年数
names = string(k55(4:end-1,1)); % 都道府県の名前
data = cell2mat(k55(4:end-1,3:end)); % 人口(数値部分)
% 年データを datetime 型に変更
timeStamp = datetime(years,1,1);
timeStamp.Format = 'yyyy'; % 表示は yyyy年
% timetable 型のデータ作成
T = array2timetable(data','RowTimes',timeStamp);
T.Properties.VariableNames = names; % 変数名指定
こんな感じのデータになります。
head(T)
Step 3: プロット
ここまでくれば
barChartRace(T);
でOK!
ただ、全データプロットすると何が何だか分かりませんので、オプションをいくつか使ってみます。
barChartRace(T,'NumDisplay',6,'NumInterp',4,...
'Position',[500 60 470 370],'ColorGroups',repmat("g",length(names),1),...
'XlabelName',"上位6都道府県の人口(千人)",...
'GenerateGIF',true,"Outputfilename",'top5.gif');
と実行すると、冒頭の GIF が出来上がり。
各種オプション
詳細は GitHub の README.md か
help barChartRace
で見てもらいたいんですが、例えば
-
'NumDisplay'
: 上位何位まで表示するか(default: 全部) -
'FontSize'
: プロットで使うのフォントサイズ(default: 15) -
'GenerateGIF'
: GIF を出力するかどうか(defaiut: false) -
'LabelNames'
:timetable
型またはtable
型変数の場合は変数の名前をそのまま使いますが、このオプションで指定可 -
'Position'
: 作成される Figure の大きさ
といったものを入れました。
Arguments
R2019b から使える Arguments (詳細:Argument Validation Functions) を使って、こんな感じでオプションの一覧を表示されるようにしてるので、オプション選択も多少楽になっているでしょうか?
barChartRace.m
の冒頭にある
arguments
inputs {mustBeNumericTableTimetable(inputs)}
options.Time (:,1) {mustBeTimeInput(options.Time,inputs)} = setDefaultTime(inputs)
options.LabelNames {mustBeVariableLabels(options.LabelNames,inputs)} = setDefaultLabels(inputs)
options.ColorGroups {mustBeVariableLabels(options.ColorGroups,inputs)} = setDefaultLabels(inputs)
(中略)
部分が対応します。網羅するのは結構大変でしたが、ちゃんと意図した変数が入力されているかどうかのチェックもするようにしてます。例えば・・
barChartRace('エラーが出るよ')
エラー: barChartRace
位置 1 の入力引数が無効です。 Input data must be either timetable, table, or numeric array (double)
1つ目の入力は timetable 型か time 型か double の配列でお願いしますとか、
barChartRace(T,'LabelNames',"これも良くないよ")
エラー: barChartRace
名前と値の引数 'LabelNames' が無効です。 The size must be same as the number of variables of inputs (46)
'LabelName'
というオプションは input と同じ変数の数 (46個) 用意してねとか、エラーの原因を表示させる感じ。自分で使う関数なら必要ないかもしれませんが、他の人にも使ってもらうならあったほうが便利かなと思い入れました。
描画のポイント1:データの内挿
各棒が順位入れ替えをする状況を表現するには順位データの内挿が必要です。barChartBar.m
の中では
%% Interpolation: Generate nInterp data point in between
time2plot = linspace(time(1),time(end),length(time)*NumInterp);
ranking2plot = interp1(time,rankings,time2plot,Method);
data2plot = interp1(time,data,time2plot,Method);
こんな感じ。interp1
関数使っています。
ここの Method
で内挿方法を指定するんですが、シンプルに線形内挿すると上のような動画。ここで spline
補間なんかしてしまうと、棒の位置がオーバーシュートするダイナミックさが加わります。いりませんね(笑)
コマンドはこんな感じ:'Method'
に 'spline'
を指定します。
barChartRace(T,'NumDisplay',6,'NumInterp',4,...
'Position',[500 60 470 370],'ColorGroups',repmat("g",length(names),1),...
'XlabelName',"上位6都道府県の人口(千人)",'Method','spline',...
'GenerateGIF',true,"Outputfilename",'top5Spline.gif');
描画のポイント2:CData
前回も nonlinopt さんにご指摘頂いた通り各棒の色は CData
プロパティで指定できます。'FaceColor'
を 'flat'
に設定することにも注意。
ただ曲者なのは CData 内の色の順番は各棒の位置と連動してしまっていること。例えば CData
をそのままにしておくと、もともとの順位(1 位:赤と 2 位:青)が逆転すると、1 位が青で 2 位が赤になっちゃいます。
例えば
hb = barh([1,2],'FaceColor','flat');
hb.CData = [0,0,1
1,0,0];
もともとの設定では上が赤、下が青ですが、その位置を変更すると・・
hb.XData = [2,1];
色の順番がそのまま。まぁ、、当然と言えば当然な気もしますが。ですので、ひと手間加えて CData
内の順番も合わせて変更する必要があります。barChartBar.m
の中では
% Set YTick position by ranking
% Set YTickLabel with variable names
[ytickpos,idx] = sort(ranking,'ascend');
handle_axes.YTick = ytickpos;
handle_axes.YTickLabel = LabelNames(idx);
% Fix CData
handle_bar.CData = colorScheme(idx,:);
といった感じで YTick
の順位変更に合わせて CData
も改めて変更しています。
描画のポイント3:棒の横の数値表示
これは text
オブジェクトを使っています。こちらの記事(MATLABのプロットでアノテーションをつける)のコメントでもやり取りがありますが、text
オブジェクトのいいところは、プロットしているデータと同じ座標系で位置指定ができること。Figure 内の相対的位置とか、Axes 内の相対的位置を計算する必要がないのは、直感的に使いやすいです。barChartRace.m
内ではこんな感じ。
% Add value string next to the bar
if IsInteger
displayText = string(round(value2plot(idx)));
else
displayText = string(value2plot(idx));
end
xTextPosition = value2plot(idx) + maxValue*0.05;
yTextPosition = ytickpos;
% NumDisplay values are used
xTextPosition = xTextPosition(1:NumDisplay);
yTextPosition = yTextPosition(1:NumDisplay);
displayText = displayText(1:NumDisplay);
handle_text = text(xTextPosition,yTextPosition,displayText,'FontSize',options.FontSize);
最大値の 5% だけ各数値から右にずらした位置に数値を表示しています。もともとのデータが整数の場合は整数に丸めて表示するオプションいれています。内挿してしまっているのでそのまま表示すると、人口が小数点表示になっちゃうから・・(多少苦しい)
おまけのポイント
表示されたプロットみると "Visualized by MATLAB" とアピールがひどい部分がありますね(笑)
これは barCharRace.m
内では以下の一行で書いているんですが、どうぞ皆さん好きな文字に変えて使ってくださいませ。
% Display created by MATLAB message
text(0.99,0.02,"Visualized by MATLAB",'HorizontalAlignment','right',...
'Units','normalized','FontSize', 10,'Color',0.5*[1,1,1]);
手抜き方法
試しにパッと作ってみるだけなら、こんな方法もあります。
- Excel からデータをコピー
- MATLAB にコピペ
barChartRace(unnamed')
まとめ
以上、簡単ではありますが barChartRace
関数内でのポイント紹介でした。
人口推移、人気プログラミング言語ランキング、各企業の時価総額ランキング、各国の出生率ランキング、各大学偏差値ランキングなどなどデータさえあれば面白そうなプロットができそうな気がします。
良ければ試してくださいね~。barChartRace
関数で困ったことあれば遠慮なくコメントください。