カテゴリー

当サイトはアフィリエイトプログラムによる収益を得ています〈景品表示法に基づく表記です)

px

【px&バブルチャート】plotly.expressで各国の収入と平均寿命の時代変化をアニメーションで

2021年10月23日

こんな人にオススメ

今は世界的に裕福に、そして長寿になってきているけど、昔はどうだったの?

各国ごとに時系列でアニメーションで見てみたい。

ということで、今回は世界各国の収入と平均寿命の時代変化をplotlypxのアニメーションで見る。

以下の記事である年のデータでバブルチャートを作成した。

bubblechart2017_pop
【plotly&バブルチャート】plotlyで各国の収入と平均寿命をバブルチャートで描く

続きを見る

また、以下の記事で年ごとに作成したが、以下の記事ではスライダーを使用した。今回はアニメーションで作成。

【plotly&バブルチャート】plotlyで各国の収入と平均寿命の時代変化をバブルチャートで描く

続きを見る

plotlyのアニメーションにはpxgoがあるけど、今回はpxを使用してグラフ化する。pxの方が簡単に作成できる分、カスタムに乏しい。

pxのアニメーションについては以下の記事で解説している。

【px&animation】plotly.expressでアニメーションのグラフを作成

続きを見る

python環境は以下。

  • Python 3.9.7
  • numpy 1.21.2
  • pandas 1.3.3
  • plotly 5.3.1
  • plotly-orca 3.4.2

運営者のメガネです。YouTubeTwitterInstagram、自己紹介はこちら、お問い合わせはこちらから。

運営者メガネ

作成したコード全文

使用するデータ

今回使用するデータは以下の3種類。

  1. 世界各国の収入
  2. 世界各国の平均寿命
  3. 世界各国の人口

データは全て「Gapminder」からの引用で、基本的にこの組織からのデータだが一部のデータは使用していない。「Based on free material from GAPMINDER.ORG, CC-BY LICENSE」、すなわちフリーな素材ということだ。

このデータを知ったきっかけは、「FACTFULNESS」という本を読んだから。この本は執筆者が人生で一番読んで良かったと思っている本で、データや事実に基づきテレビなどでは知り得ない世界の本当の姿を知るという本だ。この本の著者がデータ引用元のギャップマインダー財団の共同創立者。

各データはGapminderの「Download the data」からダウンロードすることができる。収入、平均寿命、人口はそれぞれ以下の定義だ。

  • 収入:一人当たりGDPでその単位は「2011年の固定価格のinternational dollars」
  • 平均寿命:現在の死亡パターンが同じままである場合、新生児が生きる平均年数
  • 人口:単純にその国の総人口

また、グラフの最終的な完成イメージは以下。

  • 横軸:各国の収入
  • 縦軸:各国の平均寿命
  • バブルの大きさ:各国の人口

今回は人口が多いほど赤系統のバブルに、少ないほど青系統のバブルになるように作成した。細かいことはの後に解説する。

なお、ここにギャップマインダーのグラフがある。クオリティはめちゃくちゃ高い。めちゃくちゃ軽くてびっくりする。

下準備

import re
import sys
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.io as pio

sys.path.append('../../')
import plotly_layout_template as template

まずは下準備としてのimport関連。plotly_layout_templateは自作のplotlyテンプレート。これを使用することで簡単にキレイなグラフを作成できる。

【随時更新 備忘録】plotlyのグラフ即席作成コード

続きを見る

pxのアニメーションはpandasのデータフレームを使用するのがメジャーで簡単な方法なので、今回はpandasimport

データの形式と読み込み

まずは今回使用するデータの形式とデータそのものの読み込み方を解説。もちろん色んな方法があると思うけど、個人的にはこの方法が楽だった。

データの読み込み

# 使用するscvファイル名
income_name = './income_per_person_gdppercapita_ppp_inflation_adjusted.csv'
life_expectancy_name = './life_expectancy_years.csv'
population_name = './population_total.csv'

まずはデータの読み込みから。先程のダウンロードサイトからデータをダウンロードして、pyファイルと同じディレクトリに置いた。

ファイル名は変わるかもしれないけど、とりあえず収入・平均寿命・人口のデータを使用。

pandasのcsv読み込み時にファイル名を指定してもいいけど、その場合は79文字ルールに反するので予め変数化。

【PEP8&flake8】pythonにおけるPEP8とflake8

続きを見る

これらを使用して、各データを実際に読み込む。区切り文字は明示的に','と指定、0列目は国名でこれをインデックスとした。

# データの読み込み
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)

読み込んだ内容を出力すると以下。

print(income)
#                       1800  1801  1802  1803  ...   2047   2048   2049   2050
# country                                       ...
# Afghanistan            674   674   674   674  ...   3270   3340   3410   3480
# Angola                 691   693   697   700  ...   9120   9320   9520   9720
# Albania                746   746   746   746  ...  27.8k  28.3k  28.9k  29.6k
# Andorra               1340  1340  1340  1350  ...   123k   126k   128k   131k
# United Arab Emirates  1120  1120  1120  1130  ...  84.5k  86.3k  88.1k    90k
# ...                    ...   ...   ...   ...  ...    ...    ...    ...    ...
# Samoa                 1570  1570  1570  1570  ...   9340   9540   9740   9950
# Yemen                  981   983   986   988  ...   4510   4610   4710   4810
# South Africa          1760  1740  1720  1700  ...  16.5k  16.8k  17.2k  17.6k
# Zambia                 741   743   746   747  ...   4010   4090   4180   4270
# Zimbabwe               972   973   974   975  ...   3960   4050   4130   4220

# [195 rows x 251 columns]

print(life_expectancy)
#                       1800  1801  1802  1803  ...  2097  2098  2099  2100
# country                                       ...
# Afghanistan           28.2  28.2  28.2  28.2  ...  76.4  76.5  76.6  76.8
# Angola                27.0  27.0  27.0  27.0  ...  79.6  79.7  79.9  80.0
# Albania               35.4  35.4  35.4  35.4  ...  88.0  88.2  88.3  88.4
# Andorra                NaN   NaN   NaN   NaN  ...   NaN   NaN   NaN   NaN
# United Arab Emirates  30.7  30.7  30.7  30.7  ...  83.0  83.1  83.2  83.3
# ...                    ...   ...   ...   ...  ...   ...   ...   ...   ...
# Samoa                 25.4  25.4  25.4  25.4  ...  80.5  80.6  80.7  80.8
# Yemen                 23.4  23.4  23.4  23.4  ...  77.6  77.8  77.9  78.0
# South Africa          33.5  33.5  33.5  33.5  ...  77.3  77.4  77.5  77.7
# Zambia                32.6  32.6  32.6  32.6  ...  76.7  76.8  77.0  77.1
# Zimbabwe              33.7  33.7  33.7  33.7  ...  74.0  74.2  74.3  74.4

# [195 rows x 301 columns]

print(population)
#                        1800   1801   1802   1803  ...   2097   2098   2099   2100
# country                                           ...
# Afghanistan           3.28M  3.28M  3.28M  3.28M  ...  75.6M  75.4M  75.2M  74.9M
# Angola                1.57M  1.57M  1.57M  1.57M  ...   182M   184M   186M   188M
# Albania                400k   402k   404k   405k  ...  1.17M  1.14M  1.11M  1.09M
# Andorra                2650   2650   2650   2650  ...  62.6k  62.5k  62.5k  62.4k
# United Arab Emirates  40.2k  40.2k  40.2k  40.2k  ...  12.7M  12.8M  12.8M  12.9M
# ...                     ...    ...    ...    ...  ...    ...    ...    ...    ...
# Samoa                 47.3k  47.3k  47.3k  47.3k  ...   312k   312k   311k   310k
# Yemen                 2.59M  2.59M  2.59M  2.59M  ...  53.7M  53.5M  53.4M  53.2M
# South Africa          1.45M  1.45M  1.46M  1.46M  ...  79.5M  79.4M  79.3M  79.2M
# Zambia                 747k   758k   770k   782k  ...  79.2M    80M  80.8M  81.5M
# Zimbabwe              1.09M  1.09M  1.09M  1.09M  ...    31M    31M    31M    31M

# [197 rows x 301 columns]

なるほど、NaNとk(キロ)やM(メガ)などのSI接頭辞がついているデータがある。

NaNに関してはほっといても大丈夫。グラフ化してくれないだけのはず。しかし、SI接頭辞は厄介だ。

一応、astropyを使用することでSI接頭辞を認識させることは可能だが、人口の部分でB(ビリオン=10億)が出てきてエラー。

SI接頭辞とB(ビリオン)を処理

# データ内にMとかBとかの接頭辞があるので数値に変換
def prefix_modify(val):
    val = f"{val}"
    number = re.findall(r'\\d+(?:\\.\\d+)?', val)[0]
    if 'k' in val:
        number = float(number) * 1e3
    elif 'M' in val:
        number = float(number) * 1e6
    elif 'B' in val:
        number = float(number) * 1e9

    return number

ということで接頭辞をうまく処理する方法を探していたけど、どうしてもBが処理できなかったのでムリヤリ作成。

prefix_modify関数の引数valは接頭辞の処理をしたい値で、数値の場合はのちにエラーが出るのでstrで文字列に変換。

numberは以下のような挙動。要するにデータから数値のみを抽出。小数にも対応させた。

  • 3.28M: 3.28
  • 400k: 400
  • 2650: 2650(そのまま)

あとはifでk, M, Bがあればこれらを処理する。何も接頭辞がない場合は処理せずそのままnumberを使用する。

実際にデータフレームに適用するにはapplyもしくはapplymapを使用するが、今回は列ごととかじゃなくて各データに対して適用なのでapplymap

もちろん平均寿命で1kとかはないので、平均寿命のlife_expectancyには適用しない。

# データフレーム中のSI接頭辞を数値に変換
income = income.applymap(prefix_modify)
population = population.applymap(prefix_modify)

これで接頭辞を値に変換できているので確認する。ちゃんと値が変換されている。

print(income)
#                       1800  1801  1802  ...      2048      2049      2050
# country                                 ...
# Afghanistan            674   674   674  ...      3340      3410      3480
# Angola                 691   693   697  ...      9320      9520      9720
# Albania                746   746   746  ...   28300.0   28900.0   29600.0
# Andorra               1340  1340  1340  ...  126000.0  128000.0  131000.0
# United Arab Emirates  1120  1120  1120  ...   86300.0   88100.0   90000.0
# ...                    ...   ...   ...  ...       ...       ...       ...
# Samoa                 1570  1570  1570  ...      9540      9740      9950
# Yemen                  981   983   986  ...      4610      4710      4810
# South Africa          1760  1740  1720  ...   16800.0   17200.0   17600.0
# Zambia                 741   743   746  ...      4090      4180      4270
# Zimbabwe               972   973   974  ...      4050      4130      4220

# [195 rows x 251 columns]

print(population)
#                            1800       1801  ...         2099         2100
# country                                     ...
# Afghanistan           3280000.0  3280000.0  ...   75200000.0   74900000.0
# Angola                1570000.0  1570000.0  ...  186000000.0  188000000.0
# Albania                400000.0   402000.0  ...    1110000.0    1090000.0
# Andorra                    2650       2650  ...      62500.0      62400.0
# United Arab Emirates    40200.0    40200.0  ...   12800000.0   12900000.0
# ...                         ...        ...  ...          ...          ...
# Samoa                   47300.0    47300.0  ...     311000.0     310000.0
# Yemen                 2590000.0  2590000.0  ...   53400000.0   53200000.0
# South Africa          1450000.0  1450000.0  ...   79300000.0   79200000.0
# Zambia                 747000.0   758000.0  ...   80800000.0   81500000.0
# Zimbabwe              1090000.0  1090000.0  ...   31000000.0   31000000.0

# [197 rows x 301 columns]

3データ揃っている国を抽出

income_country = set(income.index.values)
life_expectancy_country = set(life_expectancy.index.values)
population_country = set(population.index.values)

続いては収入・平均寿命・人口の3データが揃っている国名だけを抽出する作業。

というのも、データによったらこの国のデータは存在していないとかになっていることがある。これだとグラフ化がダルいので予め揃っていないデータは弾く。

setを使用すると一意的なデータを取得することができる。実際に収入を見てみると以下の国が含まれていることがわかる。

print(income_country)
# {'Algeria', 'Cape Verde', 'Lao', 'Somalia', 'Gabon', 'Slovak Republic', 'Lesotho', 'North Macedonia', 'Mauritius', 'Turkmenistan', 'Burkina Faso', 'Kyrgyz Republic', 'Eritrea', 'Syria', 'Netherlands', 'Jordan', 'Monaco', 'Canada', 'Equatorial Guinea', 'Sudan', 'Kuwait', 'Israel', 'North Korea', 'Uruguay', 'Sao Tome and Principe', 'Russia', 'Samoa', 'China', 'Kazakhstan', 'Iceland', 'South Korea', 'Singapore', 'Cuba', 'Oman', 'Papua New Guinea', 'Estonia', 'Morocco', 'Marshall Islands', 'Dominican Republic', 'Czech Republic', 'Luxembourg', 'Madagascar', 'Antigua and Barbuda', 'Sri Lanka', 'Romania', 'Paraguay', 'Finland', 'Nauru', 'Slovenia', 'Cameroon', 'El Salvador', 'Mauritania', 'Turkey', 'Peru', 'Botswana', 'Zimbabwe', 'Tunisia', 'Ireland', 'Namibia', 'Sierra Leone', 'Honduras', 'Kenya', 'India', 'Armenia', 'United Kingdom', 'Vanuatu', 'Spain', 'Tuvalu', 'Bahrain', 'Congo, Dem. Rep.', 'Seychelles', 'United Arab Emirates', 'Brazil', 'France', 'New Zealand', 'Bulgaria', 'Guinea-Bissau', 'Mongolia', 'Bhutan', 'Iraq', 'Yemen', 'Azerbaijan', 'Malaysia', 'Eswatini', 'Tanzania', 'United States', 'Saudi Arabia', 'Myanmar', 'Belarus', 'Lebanon', 'Bahamas', 'Guinea', 'San Marino', 'Rwanda', 'Tajikistan', 'Hungary', 'Denmark', 'Solomon Islands', 'Andorra', 'Fiji', 'Montenegro', 'Nigeria', 'Panama', 'Chile', 'South Africa', 'Micronesia, Fed. Sts.', 'Angola', 'Hong Kong, China', 'South Sudan', 'Bosnia and Herzegovina', 'Trinidad and Tobago', 'Togo', 'Germany', 'Benin', 'Serbia', 'Mali', 'Malta', 'Ecuador', 'Austria', 'Belize', 'Djibouti', 'Thailand', 'Malawi', 'Congo, Rep.', 'Lithuania', 'Afghanistan', 'Poland', 'Venezuela', 'Taiwan', 'Central African Republic', "Cote d'Ivoire", 'Ukraine', 'Gambia', 'Costa Rica', 'Mexico', 'Zambia', 'Chad', 'Sweden', 'Jamaica', 'Libya', 'Georgia', 'Nepal', 'Qatar', 'Mozambique', 'Bangladesh', 'Pakistan', 'Palau', 'Guatemala', 'Norway', 'Haiti', 'Cyprus', 'Senegal', 'Albania', 'Tonga', 'Niger', 'Kiribati', 'Comoros', 'Belgium', 'Brunei', 'Ethiopia', 'Italy', 'Iran', 'Egypt', 'Portugal', 'Philippines', 'Liberia', 'Croatia', 'Indonesia', 'Uzbekistan', 'Ghana', 'Switzerland', 'St. Vincent and the Grenadines', 'St. Kitts and Nevis', 'St. Lucia', 'Guyana', 'Argentina', 'Bolivia', 'Burundi', 'Cambodia', 'Vietnam', 'Maldives', 'Barbados', 'Dominica', 'Nicaragua', 'Colombia', 'Grenada', 'Greece', 'Timor-Leste', 'Australia', 'Latvia', 'Suriname', 'Moldova', 'Palestine', 'Uganda', 'Japan'}

これらを論理演算子の&で結んでやると、共通する国だけを出力してくれる。要するに揃っていないと出力されない。

ただし、setの場合は順番が適当になるので、元のアルファベット順になるようにsortedでソートしている。

# 3データ揃っているデータのみを抽出
common_country = income_country & life_expectancy_country & population_country
common_country = sorted(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', 'Hong Kong, China', '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', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Myanmar', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'North Korea', 'North Macedonia', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Samoa', 'San Marino', '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. Kitts and Nevis', 'St. Lucia', 'St. Vincent and the Grenadines', 'Sudan', 'Suriname', 'Sweden', 'Switzerland', 'Syria', 'Taiwan', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe']

3データ揃っている年を抽出

income_year = set(income.columns.values)
life_expectancy_year = set(life_expectancy.columns.values)
population_year = set(population.columns.values)

年についても同じ。3データでは何年までのデータかが異なっているので共通する年だけを抽出する。

やってることはさっきの国の時と同じ。setで一意的な国を抽出して(今回は元から一意的)、&で共通部分を抽出。

# 共通する年を抽出
common_year = income_year & life_expectancy_year & population_year
common_year = sorted(common_year)
print(common_year)
# ['1800', '1801', '1802', '1803', '1804', '1805', '1806', '1807', '1808', '1809', '1810', '1811', '1812', '1813', '1814', '1815', '1816', '1817', '1818', '1819', '1820', '1821', '1822', '1823', '1824', '1825', '1826', '1827', '1828', '1829', '1830', '1831', '1832', '1833', '1834', '1835', '1836', '1837', '1838', '1839', '1840', '1841', '1842', '1843', '1844', '1845', '1846', '1847', '1848', '1849', '1850', '1851', '1852', '1853', '1854', '1855', '1856', '1857', '1858', '1859', '1860', '1861', '1862', '1863', '1864', '1865', '1866', '1867', '1868', '1869', '1870', '1871', '1872', '1873', '1874', '1875', '1876', '1877', '1878', '1879', '1880', '1881', '1882', '1883', '1884', '1885', '1886', '1887', '1888', '1889', '1890', '1891', '1892', '1893', '1894', '1895', '1896', '1897', '1898', '1899', '1900', '1901', '1902', '1903', '1904', '1905', '1906', '1907', '1908', '1909', '1910', '1911', '1912', '1913', '1914', '1915', '1916', '1917', '1918', '1919', '1920', '1921', '1922', '1923', '1924', '1925', '1926', '1927', '1928', '1929', '1930', '1931', '1932', '1933', '1934', '1935', '1936', '1937', '1938', '1939', '1940', '1941', '1942', '1943', '1944', '1945', '1946', '1947', '1948', '1949', '1950', '1951', '1952', '1953', '1954', '1955', '1956', '1957', '1958', '1959', '1960', '1961', '1962', '1963', '1964', '1965', '1966', '1967', '1968', '1969', '1970', '1971', '1972', '1973', '1974', '1975', '1976', '1977', '1978', '1979', '1980', '1981', '1982', '1983', '1984', '1985', '1986', '1987', '1988', '1989', '1990', '1991', '1992', '1993', '1994', '1995', '1996', '1997', '1998', '1999', '2000', '2001', '2002', '2003', '2004', '2005', '2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024', '2025', '2026', '2027', '2028', '2029', '2030', '2031', '2032', '2033', '2034', '2035', '2036', '2037', '2038', '2039', '2040', '2041', '2042', '2043', '2044', '2045', '2046', '2047', '2048', '2049', '2050']

3データをデータフレームにまとめる

# 辞書でデータを入れる
dct = {
    'country': [], 'year': [],
    'income': [], 'life_expectancy': [], 'population': []
}

pxでアニメーションを作成するにはデータフレームを使用するのが楽。ということで、3データをまとめたデータフレームを作成。

作成にはdictを使用するのが楽なのでまずは必要なデータの格納先をdictで作成。keyがデータフレームの列名となる。

あとはこのdictの各keyに対応したデータをvaluelistに入れていけばいい。

for name in common_country:
    for year in common_year:
        dct['country'].append(name)
        dct['year'].append(int(year))
        dct['income'].append(float(income[year][name]))
        dct['life_expectancy'].append(float(life_expectancy[year][name]))
        dct['population'].append(float(population[year][name]))

国名を固定しつつ、年を固定し、各国と各年に該当する国名・年・収入・平均寿命・人口をlistに入れる。

データはstrになっており、この場合だと後々エラーになるのでintfloatで調節している。

最後にこのdictpandasのデータフレームに入れたら完成。

df = pd.DataFrame(dct)
print(df)
#            country  year  income  life_expectancy  population
# 0      Afghanistan  1800   674.0             28.2   3280000.0
# 1      Afghanistan  1801   674.0             28.2   3280000.0
# 2      Afghanistan  1802   674.0             28.2   3280000.0
# 3      Afghanistan  1803   674.0             28.2   3280000.0
# 4      Afghanistan  1804   674.0             28.2   3280000.0
# ...            ...   ...     ...              ...         ...
# 48940     Zimbabwe  2046  3880.0             66.9  22800000.0
# 48941     Zimbabwe  2047  3960.0             67.1  23100000.0
# 48942     Zimbabwe  2048  4050.0             67.3  23400000.0
# 48943     Zimbabwe  2049  4130.0             67.4  23700000.0
# 48944     Zimbabwe  2050  4220.0             67.6  23900000.0

# [48945 rows x 5 columns]

データの並び方としては、行方向にある国の各年のデータが並んでいく。ある国データが終われば次の行からは次の国の初めの年が始まる。

グラフ化するための関数

ということで、グラフを描くための前段階の準備は終わった。ここではグラフを描くための関数を解説。

グラフ保存用の関数

# グラフ保存用の関数
def save(fig, config, save_name):
    pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca'
    pio.write_html(fig, f"{save_name}.html", config=config,)
    pio.write_image(fig, f"{save_name}.png")

グラフ保存用の関数を定義。保存形式はhtmlとpng。configはグラフ上部のツールバーのことで、configの設定で追加や削除が可能。

【plotly&config】グラフのツールバーを編集する

続きを見る

グラフ化するための関数

def graph(save_name, step=1, drtn=200, **kwargs):

    # データが重いとき用に何データずつスキップするかを指定
    years = common_year[::step]
    # yearsの各年はstrなのでintに変換
    years = np.array(years).astype(int)
    # yearsに当てはまるyear列を持つデータフレームを作成
    data_frame = df[df['year'].isin(years)]

    # 最低限の引数を設定、追加設定はkwargsで
    fig = px.scatter(
        data_frame=data_frame,  # 使用するデータフレーム
        x='income', y='life_expectancy',  # 横・縦軸の列名
        animation_frame='year',  # アニメーション軸の列名
        animation_group='country',  # 各アニメーションで同データとみなす列名
        range_x=[500, 200000], range_y=[0, 100],  # 横・縦軸の表示範囲
        hover_data=df,  # ホバーに表示する内容=dfの内容
        **kwargs  # 追加設定
    )

    # アニメーションの進行速度を速める
    fig.layout.updatemenus[0].buttons[0].args[1]['frame']['duration'] = drtn
    # テンプレートではxanchorがrightなのでleftにしておく
    fig.update_layout(legend=dict(xanchor='left'))

    # グラフの表示と保存
    config = template.plotly_config()
    fig.show(config=config)
    save(fig=fig, config=config, save_name=save_name)

ということで、最後にグラフ化するための関数を定義しておく。引数は以下。

  • save_name: グラフの保存名
  • step: データを何年おきに作成するか
  • drtn: アニメーションの遅延の値(ms単位)
  • kwargs: グラフ化に際しての追加条件

今回のアニメーションはデータが重くなることもあるので、stepで何年おきにするかを指定できるようにした。

また、アニメーションの動きが遅くなるとイライラするので遅延も指定可能に。

さらに、kwargspx.scatterの引数の追加を可能とした。kwargsについては以下。

px.scatterはデータフレームを基準にどの列をどの引数にするかを指定する。列名になかったらエラーとなるので注意。

この関数の注意点としては凡例に国名を反映させる場合、isinの影響で順番がバラバラなので色分けがぐちゃぐちゃになるということ。

isinsetと同じ論理演算子なので順番は考えない。orderを指定できる引数があればいいがないので仕方ない。

実際にグラフ化


実際にグラフ化する。まずはkwargsを指定せずに最低限のデータのみでプロット。この場合は全てのプロットが同じ色なのでわかりにくい。

さらにマーカーサイズも同じなので何が何だかわからん。ということで、以下でkwragsで追加設定を行う。

# デフォルトのアニメーション
graph(save_name='animation', step=5)

国ごとに色分け