こんな人にオススメ
日本は極端に平和でお金持ちでさらには長生きの国っていうのは知っている。けどお金持ちだと美味しいものばかり食べて栄養が偏って寿命が短いパターンもあるんじゃない?収入と平均寿命って相関があるの?
今回はアニメーションを使ってわかりやすく知りたい!
今回は以前に
plotly
で作成した「世界各国の収入と平均寿命の関係性のスライダー時系列版」のグラフをアニメーションで作成する。スライダーで作成した版は以下参照。
-
-
【plotly&バブルチャート】plotlyで各国の収入と平均寿命の時代変化をバブルチャートで描く
続きを見る
また、単年版の「2017年における世界各国の収入と平均寿命の関係性」については以下参照。
-
-
【plotly&バブルチャート】plotlyで各国の収入と平均寿命をバブルチャートで描く
続きを見る
スライダー版の時はファイル容量の関係で1975年から2020年までを5年ごととし合計10データだったが、今回はさらにファイル容量が重くなって1820年から2020年までを50年ごととした合計5データでしか紹介できない。
また、plotly
のバグか設定ミスか分からないが変な挙動を取る部分もあるのでそこは後ほど指摘する。
python環境は以下。
- Python 3.9.2
- numpy 1.20.1
- matplotlib 3.3.4
- plotly 4.14.3
- plotly-orca 3.4.2
- pandas 1.2.3
使用するデータと完成グラフイメージ
使用するデータ
今回使用するデータは以下の3種類。
- 世界各国の収入
- 世界各国の平均寿命
- 世界各国の人口
データは全て「Gapminder」からの引用で、基本的にこの組織からのデータだが一部のデータは使用していない。「Based on free material from GAPMINDER.ORG, CC-BY LICENSE」、すなわちフリーな素材ということだ。
このデータを知ったきっかけは、「FACTFULNESS」という本を読んだから。この本は執筆者が人生で一番読んで良かったと思っている本で、データや事実に基づきテレビなどでは知り得ない世界の本当の姿を知るという本だ。この本の著者がデータ引用元のギャップマインダー財団の共同創立者。
各データはGapminderの「Download the data」からダウンロードすることができる。収入、平均寿命、人口はそれぞれ以下の定義だ。
- 収入:一人当たりGDPでその単位は「2011年の固定価格のinternational dollars」
- 平均寿命:現在の死亡パターンが同じままである場合、新生児が生きる平均年数
- 人口:単純にその国の総人口
完成グラフイメージ
グラフの最終完成イメージは以下の通り。
- 横軸:各国の収入
- 縦軸:各国の平均寿命
- バブルの大きさ:各国の人口
今回は人口が多いほど赤系統のバブルに、少ないほど青系統のバブルになるように作成した。細かいことはの後に解説する。そして「Play」「Pause」でアニメーションを起動させることができる。
なお、「https://tools-legacy.gapminder.org/tools/#$state$marker$axis_y$domainMin:null&domainMax:null&zoomedMin:null&zoomedMax:null&spaceRef:null;;;&chart-type=bubbles」にギャップマインダーのグラフがある。クオリティはめちゃくちゃ高い。データ数が多いのに滑らかに動くのですごいと思う。
グラフ作成コード
plotly
のバブルチャートの作成には特別な関数などは使わなくて、基本的にはgo.Scatter
でmode
を'markers'
に設定。その後、マーカーの大きさを変更する、ただそれだけ。以下に具体的な設定を書く。
mode='markers'
:プロットはマーカーのみmarker=dict(size=(人口の多さで設定))
:人口の多さに応じてマーカーのサイズを大きくするmarker=dict(line=dict(color='black', width=1))
:マーカーの境目は黒にして、他のデータと混ざらないように設定
スライダー版と同様、任意の年から任意の年まで、任意の年の間隔で作成する。例えば、1800年から1900年までを2年ごとのように。また、plotly
の公式サイトの「Intro to Animations in Python」に書いている方法ではなく、それを執筆者なりに書き換えたコードを示す。もしかしたらこの方法だと変な挙動を示すのかもしれない。
全体コード
今回作成したコードでは以前までのようにその年毎の人口順にデータを並び替えておらず、指定した年の最新の人口を基準にデータを並び替えている。上の例で言えば2020年時点での人口を基準に作成している。各年での人口順に並び替えた場合、アニメーションでは挙動がミスリーディングを引き起こす原因になる。これについては後ほどコード紹介、解説を行う。
以下よりそれぞれの項目について解説していく。
下準備
まずは必要なライブラリのimport
やテンプレートの読み込み。テンプレートに関しては以下の記事参照。
-
-
【随時更新 備忘録】plotlyのグラフ即席作成コード
続きを見る
import sys import numpy as np import matplotlib.cm as cm import plotly import plotly.graph_objects as go import plotly.io as pio import pandas as pd sys.path.append('../../') import plotly_layout_template as template
使用するデータの読み込み
今回使用するデータは全てcsv形式でpyファイルと同じディレクトリに入れている。ファイルの読み込みはpandasを使用し、0列目の国名をインデックスにしてヘッダーを年にした。
# 使用するファイル名 # <https://www.gapminder.org/data/> より引用 income_name = './income_per_person_gdppercapita_ppp_inflation_adjusted.csv' life_expectancy_name = './life_expectancy_years.csv' population_name = './population_total.csv' # ファイルを読み込み income = pd.read_csv(income_name, sep=',', index_col=0) life_expectancy = pd.read_csv(life_expectancy_name, sep=',', index_col=0) population = pd.read_csv(population_name, sep=',', index_col=0)
income
は以下のようなDataFrame
になっている。
print(income) # 1800 1801 1802 1803 1804 ... 2036 2037 2038 2039 2040 # country ... # Afghanistan 603 603 603 603 603 ... 2820 2880 2940 3000 3060 # Albania 667 667 667 667 667 ... 21500 21900 22300 22800 23300 # Algeria 715 716 717 718 719 ... 15800 16100 16500 16800 17100 # Andorra 1200 1200 1200 1200 1210 ... 81500 83100 84800 86500 88300 # Angola 618 620 623 626 628 ... 6750 6880 7020 7170 7310 # ... ... ... ... ... ... ... ... ... ... ... ... # Venezuela 1210 1200 1200 1190 1190 ... 9110 9300 9490 9680 9880 # Vietnam 778 778 778 778 778 ... 13300 13500 13800 14100 14400 # Yemen 877 879 882 884 887 ... 3570 3640 3720 3790 3870 # Zambia 663 665 667 668 670 ... 3860 3930 4010 4100 4180 # Zimbabwe 869 870 871 872 873 ... 2900 2960 3020 3080 3140 # [193 rows x 241 columns]
life_expectancy
は以下。
print(life_expectancy) # 1800 1801 1802 1803 1804 ... 2096 2097 2098 2099 2100 # country ... # Afghanistan 28.2 28.2 28.2 28.2 28.2 ... 77.1 77.3 77.4 77.5 77.7 # Albania 35.4 35.4 35.4 35.4 35.4 ... 87.9 88.0 88.1 88.2 88.3 # Algeria 28.8 28.8 28.8 28.8 28.8 ... 88.8 88.9 89.0 89.1 89.2 # Andorra NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN # Angola 27.0 27.0 27.0 27.0 27.0 ... 79.4 79.5 79.7 79.8 79.9 # ... ... ... ... ... ... ... ... ... ... ... ... # Venezuela 32.2 32.2 32.2 32.2 32.2 ... 86.9 87.0 87.1 87.2 87.3 # Vietnam 32.0 32.0 32.0 32.0 32.0 ... 84.8 84.9 85.0 85.2 85.3 # Yemen 23.4 23.4 23.4 23.4 23.4 ... 77.9 78.0 78.2 78.3 78.4 # Zambia 32.6 32.6 32.6 32.6 32.6 ... 77.6 77.7 77.8 78.0 78.1 # Zimbabwe 33.7 33.7 33.7 33.7 33.7 ... 75.1 75.3 75.4 75.5 75.7 # [187 rows x 301 columns]
population
は以下のDataFrame
として作成した。
print(population) # 1800 1801 1802 ... 2098 2099 2100 # country ... # Afghanistan 3280000 3280000 3280000 ... 75400000 75200000 74900000 # Albania 400000 402000 404000 ... 1140000 1110000 1090000 # Algeria 2500000 2510000 2520000 ... 70700000 70700000 70700000 # Andorra 2650 2650 2650 ... 62500 62500 62400 # Angola 1570000 1570000 1570000 ... 184000000 186000000 188000000 # ... ... ... ... ... ... ... ... # Venezuela 1000000 978000 957000 ... 34500000 34400000 34200000 # Vietnam 4000000 4100000 4200000 ... 98100000 97800000 97400000 # Yemen 2590000 2590000 2590000 ... 53500000 53400000 53200000 # Zambia 747000 758000 770000 ... 80000000 80800000 81500000 # Zimbabwe 1090000 1090000 1090000 ... 31000000 31000000 31000000 # [195 rows x 301 columns]
国名の抽出
上のDataFrameを見ると分かるように、ヘッダの数や行数が異なっている。ヘッダについては2040年までか2100年までかで数が違うが、行数については一部のデータには存在しない国名もある。ここでは収入、平均寿命、人口の3種類のデータが揃っている国に絞ってグラフを作成したいので、国名を抽出した。
# 国名を抽出 income_country = set(income.index.values) life_expectancy_country = set(life_expectancy.index.values) population_country = set(population.index.values) # 共通する国名だけ抽出 # 例えばincomeには「Monaco」があるが、life_expectancyにはないので全てにあるものだけ抽出 common_country = income_country & life_expectancy_country & population_country common_country = sorted(list(common_country))
共通する国名として設定した変数common_country
は以下のようになっている。
print(common_country) # ['Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cambodia', 'Cameroon', 'Canada', 'Cape Verde', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Congo, Dem. Rep.', 'Congo, Rep.', 'Costa Rica', "Cote d'Ivoire", 'Croatia', 'Cuba', 'Cyprus', 'Czech Republic', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Eswatini', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kuwait', 'Kyrgyz Republic', 'Lao', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Micronesia, Fed. Sts.', 'Moldova', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar', 'Namibia', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palestine', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Samoa', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovak Republic', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Korea', 'South Sudan', 'Spain', 'Sri Lanka', 'St. Lucia', 'St. Vincent and the Grenadines', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe']
任意の年から任意の年まで任意の年間隔でデータを作成する
今回は任意のデータ取得開始年・任意のデータ取得終了年・任意のデータ取得間隔を入力することで、その条件に合ったデータを抽出することができる。
def set_data_dct( x: float, y: float, name: str, rgba: str, pop: int, size: float, year: int, visible: bool): """プロット点の設定を出力 Parameters ---------- x : float incomeの値 y : float 平均寿命の値 name : str 国名 rgba : str (R, G, B, A)の値 pop : int 人口の値 size : float プロットマーカーのサイズ調整のために割る値 visible : bool 表示するか否か Returns ------- dict プロット点の情報を入れたdict """ data_dct = dict( mode='markers', x=(x,), y=(y,), name=name, marker=dict( symbol='circle', color=f"rgba{rgba}", # マーカーのサイズ調整 size=np.sqrt(pop) / size, line=dict(color='black', width=1), ), hoverlabel=dict( font=dict( family='Times New Roman', size=15 ) ), hovertemplate=f"{name}: {year} " + 'income: %{x} ' + 'life expectancy: %{y} ' + f"population: {pop} " + "", visible=visible, ) return data_dct
この関数のそれぞれの引数に条件を入れることで、その条件に合ったデータを出力することができる。
start
:データ作成開始年end
:データ作成終了年step
:データ作成の年間隔
例えば、1975年から2020年までを5年ごとでデータを作成したい場合は以下のように設定する。
years, data = set_year( start=1975, end=2020, step=5, )
この設定だと以下の様な出力を得ることができる。dataについては長すぎるので途中省略しているが、イメージは年、3項目、国名のネスト。
print(years) # [1975 1980 1985 1990 1995 2000 2005 2010 2015 2020] print(data) # {1975: {'income': {'Afghanistan': 2150, 'Albania': 4080, ...}, 'life_expectancy': {'Afghanistan': 47.1, 'Albania': 69.8, ...}, 'population': {'Afghanistan': 12700000, 'Albania': 2410000, ...}}, 1980: {'income': }
グラフ化
最後にグラフ化。全体は長いけど部分ずつ説明していく。まずは作成するバブルチャートのプロット設定を行うdef
から。今回はアニメーション用で別にプロット設定が必要となるので予め共通設定としてdef
で関数を設定した。出力は辞書型。
def set_data_dct( x: float, y: float, name: str, rgba: str, pop: int, size: float, year: int, visible: bool): """プロット点の設定を出力 Parameters ---------- x : float incomeの値 y : float 平均寿命の値 name : str 国名 rgba : str (R, G, B, A)の値 pop : int 人口の値 size : float プロットマーカーのサイズ調整のために割る値 visible : bool 表示するか否か Returns ------- dict プロット点の情報を入れたdict """ data_dct = dict( mode='markers', x=(x,), y=(y,), name=name, marker=dict( symbol='circle', color=f"rgba{rgba}", # マーカーのサイズ調整 size=np.sqrt(pop) / size, line=dict(color='black', width=1), ), hoverlabel=dict( font=dict( family='Times New Roman', size=15 ) ), hovertemplate=f"{name}: {year} " + 'income: %{x} ' + 'life expectancy: %{y}' + f"population: {pop} " + "", visible=visible, ) return data_dct
指定した年とそれに合ったデータを定義。これは前節と同じ。
yearw, data = set_year( start=start, end=end, step=step, )
今回は指定した最終年での人口を降順でデータプロットの順番とするので、yearsのループ内では人口の降順の配列は作成しない。
# データ最後の年を基準にする pop_latest = data[max(years)]['population'] # 人口を降順にソートし、国名を抽出 sorted_pop = list( dict(sorted(pop_latest.items(), key=lambda x: x[1], reverse=True)) ) # 降順の人口 sorted_pop_val = sorted(list(pop_latest.values())) print(sorted_pop) # ['China', 'India', 'United States', 'Indonesia', 'Pakistan', 'Brazil', 'Nigeria', 'Bangladesh', 'Russia', 'Mexico', 'Japan', 'Ethiopia', 'Philippines', 'Egypt', 'Vietnam', 'Congo, Dem. Rep.', 'Turkey', 'Iran', 'Germany', 'Thailand', 'United Kingdom', 'France', 'Italy', 'Tanzania', 'South Africa', 'Myanmar', 'Kenya', 'South Korea', 'Colombia', 'Spain', 'Uganda', 'Argentina', 'Algeria', 'Sudan', 'Ukraine', 'Iraq', 'Afghanistan', 'Poland', 'Canada', 'Morocco', 'Saudi Arabia', 'Uzbekistan', 'Peru', 'Angola', 'Malaysia', 'Mozambique', 'Ghana', 'Yemen', 'Nepal', 'Venezuela', 'Madagascar', 'Cameroon', "Cote d'Ivoire", 'North Korea', 'Australia', 'Niger', 'Sri Lanka', 'Burkina Faso', 'Mali', 'Romania', 'Chile', 'Malawi', 'Kazakhstan', 'Zambia', 'Guatemala', 'Ecuador', 'Syria', 'Netherlands', 'Cambodia', 'Senegal', 'Chad', 'Somalia', 'Zimbabwe', 'Guinea', 'Rwanda', 'Benin', 'Burundi', 'Tunisia', 'Bolivia', 'Belgium', 'Haiti', 'Cuba', 'South Sudan', 'Dominican Republic', 'Czech Republic', 'Greece', 'Jordan', 'Portugal', 'Azerbaijan', 'Sweden', 'Honduras', 'United Arab Emirates', 'Hungary', 'Tajikistan', 'Belarus', 'Austria', 'Papua New Guinea', 'Serbia', 'Israel', 'Switzerland', 'Togo', 'Sierra Leone', 'Lao', 'Paraguay', 'Bulgaria', 'Libya', 'Lebanon', 'Nicaragua', 'Kyrgyz Republic', 'El Salvador', 'Turkmenistan', 'Singapore', 'Denmark', 'Finland', 'Congo, Rep.', 'Slovak Republic', 'Norway', 'Oman', 'Palestine', 'Costa Rica', 'Liberia', 'Ireland', 'Central African Republic', 'New Zealand', 'Mauritania', 'Panama', 'Kuwait', 'Croatia', 'Moldova', 'Georgia', 'Eritrea', 'Uruguay', 'Bosnia and Herzegovina', 'Mongolia', 'Armenia', 'Jamaica', 'Albania', 'Qatar', 'Lithuania', 'Namibia', 'Gambia', 'Botswana', 'Gabon', 'Lesotho', 'North Macedonia', 'Slovenia', 'Guinea-Bissau', 'Latvia', 'Bahrain', 'Equatorial Guinea', 'Trinidad and Tobago', 'Estonia', 'Timor-Leste', 'Mauritius', 'Cyprus', 'Eswatini', 'Djibouti', 'Fiji', 'Comoros', 'Guyana', 'Bhutan', 'Solomon Islands', 'Montenegro', 'Luxembourg', 'Suriname', 'Cape Verde', 'Maldives', 'Malta', 'Brunei', 'Belize', 'Bahamas', 'Iceland', 'Vanuatu', 'Barbados', 'Sao Tome and Principe', 'Samoa', 'St. Lucia', 'Kiribati', 'Micronesia, Fed. Sts.', 'Grenada', 'St. Vincent and the Grenadines', 'Tonga', 'Seychelles', 'Antigua and Barbuda', 'Andorra', 'Dominica', 'Marshall Islands'] print(sorted_pop_val) # [1440000000, 1380000000, 331000000, 274000000, 221000000, 213000000, 206000000, 165000000, 146000000, 129000000, 126000000, 115000000, 110000000, 102000000, 97300000, 89600000, 84300000, 84000000, 83800000, 69800000, 67900000, 65300000, 60500000, 59700000, 59300000, 54400000, 53800000, 51300000, 50900000, 46800000, 45700000, 45200000, 43900000, 43800000, 43700000, 40200000, 38900000, 37800000, 37700000, 36900000, 34800000, 33500000, 33000000, 32900000, 32400000, 31300000, 31100000, 29800000, 29100000, 28400000, 27700000, 26500000, 26400000, 25800000, 25500000, 24200000, 21400000, 20900000, 20300000, 19200000, 19100000, 19100000, 18800000, 18400000, 17900000, 17600000, 17500000, 17100000, 16700000, 16700000, 16400000, 15900000, 14900000, 13100000, 13000000, 12100000, 11900000, 11800000, 11700000, 11600000, 11400000, 11300000, 11200000, 10800000, 10700000, 10400000, 10200000, 10200000, 10100000, 10100000, 9900000, 9890000, 9660000, 9540000, 9450000, 9010000, 8950000, 8740000, 8660000, 8650000, 8280000, 7980000, 7280000, 7130000, 6950000, 6870000, 6830000, 6620000, 6520000, 6490000, 6030000, 5850000, 5790000, 5540000, 5520000, 5460000, 5420000, 5110000, 5100000, 5090000, 5060000, 4940000, 4830000, 4820000, 4650000, 4310000, 4270000, 4110000, 4030000, 3990000, 3550000, 3470000, 3280000, 3280000, 2960000, 2960000, 2880000, 2880000, 2720000, 2540000, 2420000, 2350000, 2230000, 2140000, 2080000, 2080000, 1970000, 1890000, 1700000, 1400000, 1400000, 1330000, 1320000, 1270000, 1210000, 1160000, 988000, 896000, 870000, 787000, 772000, 687000, 628000, 626000, 587000, 556000, 541000, 442000, 437000, 398000, 393000, 341000, 307000, 287000, 219000, 198000, 184000, 119000, 115000, 113000, 111000, 106000, 98300, 97900, 77300, 72000, 59200]
次はいよいよプロットデータの作成。プロットデータの下準備を先に説明する。年毎にframe_dct
を作成し、その中にデータと名称、そしてレイアウトの設定として年を識別出来るタイトル情報を入れている。次にその年において国名をループさせていき、色を決定。色は指定した年の最新の人口を基準に作成。
plot = {} frames = [] count = 0 for year in years: frame_dct = dict( data=[], name=f"{year}", layout=dict(title_text=f"income vs life expectancy: {year}"), ) plot[year] = [] for name in sorted_pop: pop = data[year]['population'][name] element = pop_latest[name] # 各国の人口は何番目か index = sorted_pop_val.index(element) # 各国の人口の番数を規格化 norm = (index - 0) / (len(sorted_pop_val) - 0) # RGB決める rgba = tuple(X * 255 for X in cm.jet_r(norm)[:-1]) # 透明度追加 rgba += (0.7,) size = 1e2 if not blog else 4e2
frame_dct
のdata
とプロットするデータにそれぞれset_data_dct
での辞書を格納する。この時、visible
はframe_dct
ではTrue
、プロットするデータではFalse
にしないとバグる。最後に全データとして何データあるのかをカウントする用のcount
を+1
し、frames
にframe_dct
を入れる。
# frame作成 frame_dct['data'].append( set_data_dct( x=data[year]['income'][name], y=data[year]['life_expectancy'][name], name=name, rgba=rgba, pop=pop, size=size, year=year, visible=True, ) ) # plotデータ作成 d = go.Scattergl( set_data_dct( x=data[year]['income'][name], y=data[year]['life_expectancy'][name], name=name, rgba=rgba, pop=pop, size=size, year=year, visible=False, ) ) plot[year].append(d) count += 1 frames.append(frame_dct)
初期グラフを非表示から表示にしたいので、一番古い年のvisible
をTrue
に変更。
# 初期表示時のグラフを非表示から表示にする for p in plot[min(years)]: p['visible'] = True
アニメーションの再生・停止ボタンを作成する。ボタンはupdatemenus
で作成する。再生は「Play
」、停止は「Pause
」とし、それぞれでフレーム間の間隔(duration
)を調整したり、アニメーションの動き方(easing
)を設定したりできる。また、redraw=Trueにすることで各年でのタイトルを変更することができる。しかし、この設定をするとアニメーションがカクカクになる。
ボタンについては以下参照。
続きを見る 続きを見る
【plotly&ボタン】plotlyのupdatemenusにbuttonsを追加
【plotly&ボタン】plotlyのupdatemenusに2回押し対応のbuttonsを追加
play = dict( label="Play", method="animate", args=[ None, dict( # durationは各フレーム間の時間(小さくすると高速で動く) frame=dict(duration=1000, redraw=True), fromcurrent=True, transition={ # plot点の移動の遅延(数字が小さくなるとキビキビになる) "duration": 1000, "easing": "cubic-in-out", }, ) ], ) pause = dict( label="Pause", method="animate", args=[ None, dict( frame=dict(duration=0, redraw=True), mode="immediate", transition=dict(duration=0), ), ], ) updatemenus = [ dict( buttons=[play, pause], direction="left", showactive=False, type='buttons', x=0, xanchor="left", y=-0.1, yanchor="top", ) ]
スライダーを追加することで、ユーザーが任意のタイミングで任意の年の情報を得ることができる。また、スライダーとボタンのタイミングを一致させないと、プロットは2020年なのにその瞬間のスライダーは1970年のままということになりかねない。スライダーでのredraw=Trueとしても特に何も変わらなかった。
スライダーについては以下参照。
-
-
【plotly&スライダー】plotlyのslidersにスライダーを追加
続きを見る
sliders = dict( active=0, currentvalue=dict( font=dict(size=20), prefix="Year: ", visible=True, ), transition=dict(duration=1000, easing='linear'), len=0.9, x=0.1, xanchor='left', y=-0.05, yanchor='top', steps=[], ) for year in years: slider_step = dict( label=f"{year}", method="animate", args=[ [year], dict( frame=dict(duration=1000, redraw=False), mode="immediate", transition=dict(duration=1000), title=f"income vs life expectancy: {year}", ), ], ) sliders["steps"].append(slider_step) sliders = [sliders]
レイアウトを設定。この部分に関しては今まで通り。上に書いた記事などを参照していただきたい。
layout = go.Layout( template=template.plotly_layout(), title=dict( text=f"income vs life expectancy: {start}", ), xaxis=dict( title='income [GDP per capita, constant PPP dollars]', type='log', range=[np.log10(200), np.log10(2e5)], showspikes=True, spikecolor='black', spikethickness=2, spikedash='dot', ), yaxis=dict( title='Life expectancy at birth', range=[0, 100], showspikes=True, spikecolor='black', spikethickness=2, spikedash='dot', ), legend=dict( title=f"country: {save_name}", xanchor='left', ), sliders=sliders, updatemenus=updatemenus, )
グラフの作成について、今回はdata
, layout
だけではなくframes
も存在しているのでそれも追加。保存はいつも通り。グラフのconfig
のeditable
はスマホでの凡例のスクロール時に凡例が移動するのを防ぐためにOFFにしている。
# 作成したデータの入った配列と、レイアウトの配列を図にする fig = go.Figure( data=sum(list(plot.values()), []), layout=layout, frames=frames ) # 作成したグラフを保存 plotly.offline.iplot(fig, config=template.plotly_config(tf=False),) pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca' pio.write_html( fig, f"plotly_bubblechart_animation{start}_{end}_{step}_{save_name}.html", auto_open=False, config=template.plotly_config(tf=False), ) pio.write_image( fig, f"plotly_bubblechart_animation{start}_{end}_{step}_{save_name}.png" )
実際のグラフ
前述の通り、ファイルサイズが大きいので今回は1820年から2020年までを50年おきに作成した。
ここまで順番に読んでくださった方は気づかなかったかもしれないが、実はグラフ表示時に勝手にアニメーションが動き始めるという現象が起きている。
バグか何かだとは思うが、上述のplotlyの公式サイトのグラフではこの現象は起きないのでグラフ作成コードの書き方に問題があるのかもしれない。一応plotlyのcommunityの質問場?で同じような現象が起きている人がいるのでバグかもしれない。
もちろん開始・終了年、年間隔は調整することができる。以下の例では1800年から2020年までを10年おきに作成したもの。このレベルになるとファイルが重いせいか、グラフが滑らかに動かない。
各年で人口順に並び替える
最後に以前までのコードで使用した、各年で人口順にデータを並び替えるということについて解説する。全体コードは下に書いた通り。変更点は以下のコードの位置がyearのループ内にあるということ。すなわち、基準となる人口は各年で変わっていくということ。
pop_year = data[year]['population'] sorted_pop = list( dict(sorted(pop_year.items(), key=lambda x: x[1], reverse=True)) ) sorted_pop_val = sorted(list(pop_year.values()), reverse=True)
このコードで完成するグラフは以下のもの。ぱっと見では何も変更点がないように見えるが、実際に動かすとバブルが行ったり来たりしていることがわかる。