SeabornとMatplotlibを使用して美しい年齢分布グラフを作成する方法(アニメーションを含む)

How to create a beautiful age distribution graph using Seaborn and Matplotlib (including animation)

グラフのチュートリアル

国と地域の人口統計を視覚化する

Graphs created by the author

今日は、matplotlibseabornを使用して上記のような美しい年齢分布グラフを作成する方法を紹介したいと思います。

年齢分布グラフは、国や地域の人口統計を視覚化するために優れています。興味深いですが、デフォルトのSeaborn + Matplotlibグラフは十分に見栄えがしません。

このチュートリアルで学ぶことは以下の通りです:

  • Seabornスタイルの作成方法
  • 軸を改善して読みやすく情報量を増やす方法
  • タイトルと美しい凡例の追加
  • Matplotlib図をPIL画像に変換して外部余白を追加する方法
  • 複数の画像グリッドの作成(上記の例のように)
  • 人口が時間の経過とともにどのように変化するかを示すタイムラプスアニメーションの作成

このGitHubリポジトリでデータとコードを見つけることができます。

さあ、始めましょう。

データのクイックな概要

元のデータは、Creative Commons Attribution 4.0でライセンスされた世界銀行の人口見積もりと予測データセットから取得されたもので、1960年から2021年までの実際の値と、2050年までの公式予測を含んでいます。

GitHubリポジトリでは、データを処理し、4つの別々のCSVファイルを作成しました。したがって、グラフの作成に集中できます。

女性用と男性用の2つのファイルには、人口が絶対数で含まれています。

Screenshot by the author

他の2つは、総人口の比率を表す値を持っています。例えば、以下のスクリーンショットでは、1960年にバーレーンの人口のうち、70歳から74歳の間の人口はわずか0.336%しかいなかったことがわかります。

Screenshot by the author

データセットには、00-0405-0910-1415-1920-2425-2930-3435-3940-4445-4950-5455-5960-6465-6970-7475-7980+の17歳階級があります。

そして、250以上の国と地域がありますので、あなたの興味を引く年齢分布グラフを作成してください。

最初の年齢分布グラフの作成

これでデータを理解したので、Seabornのデフォルト設定で単純なグラフを作成できます。女性には赤色、男性には青色を使用しています。

それは少しステレオタイプかもしれませんが、グラフを理解しやすくすることは非常に重要であり、色はその最初の解釈にとって重要です。

唯一の「トリック」は、男性の値にマイナスを掛けて、青い棒が反対方向に行くようにすることです。

以下は、グラフを作成するための関数です。

def create_age_distribution(female_df, male_df, country, year):    df_f = female_df[female_df.country_name == country].loc[::-1]    df_m = male_df[male_df.country_name == country].loc[::-1]        ax = sns.barplot(y=df_m["indicator_name"], x=df_m[year] * -1, orient="h", color=MALE_COLOR)    ax = sns.barplot(y=df_f["indicator_name"], x=df_f[year], orient="h", color=FEMALE_COLOR)        return ax

そして、私がそれを使う方法です。

fig = plt.figure(figsize=(10, 7))ax = create_age_distribution(    female_df=population_female,    male_df=population_male,    country="World",    year="2021")plt.show()

これは2021年の世界の年齢分布グラフの結果です。すべてのデータを示していますが、見栄えが悪く理解するのが難しいです。

Graph created by the author

それを改善しましょう。

Seabornスタイルの作成

Seabornの最高の部分は、sns.set_style()を使用して独自のスタイルを簡単に作成できることです。 それはいくつかの異なる値を持つ辞書を取ります。

このチュートリアルでは、次の関数を作成して、さまざまなスタイルをすばやく試すことができます。

def set_seaborn_style(font_family, background_color, grid_color, text_color):    sns.set_style({        "axes.facecolor": background_color,        "figure.facecolor": background_color,        "axes.labelcolor": text_color,        "axes.edgecolor": grid_color,        "axes.grid": True,        "axes.axisbelow": True,        "grid.color": grid_color,        "font.family": font_family,        "text.color": text_color,        "xtick.color": text_color,        "ytick.color": text_color,        "xtick.bottom": False,        "xtick.top": False,        "ytick.left": False,        "ytick.right": False,        "axes.spines.left": False,        "axes.spines.bottom": True,        "axes.spines.right": False,        "axes.spines.top": False,    })

さらに制御するものが欲しい場合は、ここでは関心がないいくつかのオプションを省略し、同じ色を複数の場所で再利用しています。

関数を実行するには、背景、グリッド、およびテキストの色を選択する必要があります。 私は背景色を持つチャートが好きで、ページからより目立ちます。 白い背景は良く見えるかもしれませんが、私のスタイルではありません。

新しいカラースキームを作成する場合、私はしばしば好きな色を1つ見つけてから始めます。 見つけるのに良い場所はCanva Color PalettesまたはColorHuntです。

いくつかの好きな色を見つけた後、Coolorsで追加の色を生成します。

ここに、このチュートリアルで使用するメインカラーパレットがあります。

Screenshot by the author

今、私たちの新しい色でset_seaborn_style()を実行し、フォントにPT Monoを選択しました。

FEMALE_COLOR = "#F64740"MALE_COLOR = "#05B2DC"set_seaborn_style(    font_family="PT Mono",    background_color="#253D5B",    grid_color="#355882",    text_color="#EEEEEE")

ここに、グラフが現在どのように見えるかがあります。

Graph created by the author

これは以前より明らかに改善されていますが、情報が不足しており、理解するのはまだ難しいです。

軸を修正して続けましょう。

軸の改善

色が良く見えるようになったので、グラフをより情報豊富にする時間です。

ここで、私がやりたい3つのことがあります。

  • 軸ラベルを削除する。これらは情報を追加しないためです。
  • x軸の値をフォーマットして、より情報を提供します
  • テキストを大きくして、グラフが小さなスクリーンでもよく見えるようにします

このソリューションには、2つの関数が含まれています。

最初に、create_x_labels() 関数は、2番目の箇条書きに対処し、国の人口に基づいてx軸を素早く適応させたり、絶対数値の代わりに比率を使用したい場合に役立ちます。

def create_x_labels(ax, xformat):    if xformat == "billions":        return ["{}B".format(round(abs(x / 1e9))) for x in ax.get_xticks()[1:-1]]    elif xformat == "millions":        return ["{}M".format(round(abs(x / 1e6))) for x in ax.get_xticks()[1:-1]]    elif xformat == "thousands":        return ["{}K".format(round(abs(x / 1e3))) for x in ax.get_xticks()[1:-1]]    elif xformat == "percentage":        return ["{}%".format(round(abs(x), 1)) for x in ax.get_xticks()[1:-1]]

そして2つ目に、format_ticks() 関数があり、最初の3つの箇条書きに対処し、create_x_labels() を呼び出します。

def format_ticks(ax, xformat, xlim=(None, None)):    ax.tick_params(axis="x", labelsize=12, pad=8)    ax.tick_params(axis="y", labelsize=12)    ax.set(ylabel=None, xlabel=None, xlim=xlim)        plt.xticks(        ticks=ax.get_xticks()[1:-1],        labels=create_x_labels(ax, xformat)    )

xlim パラメーターは、2つの異なる年齢分布を比較する場合に必要です。空白にしておくと、軸はデータの値に適応し、バーが全体の軸に広がります。

私はチャートを作成する際に、これらの関数を追加します。以前と全く同じですが、最後に format_tricks() を追加します。

fig = plt.figure(figsize=(10, 7))ax = create_age_distribution(    female_df=population_female,    male_df=population_male,    country="World",    year="2021")# New functionsformat_ticks(ax, xformat="millions")plt.show()

以下は新しいグラフの見た目です。

Graph created by the author

xformat="percentage" に設定し、 population_ratio_malepopulation_ratio_female を使用してパーセンテージ形式をテストしてみることもできます。また、 xlim=(-10, 10) を設定しました。

Graph created by the author

見た目は良いですが、さらに改善できます。

タイトルと凡例の追加

今度は、改善したい2つの明らかな点があります。

  • グラフを説明するタイトルを追加する
  • バーが表すものを説明する凡例を追加する

凡例を作成するには、xとyパラメーターを取り、場所を定義します。

def add_legend(x, y):     patches = [        Patch(color=MALE_COLOR, label="Male"),        Patch(color=FEMALE_COLOR, label="Female")    ]        leg = plt.legend(        handles=patches,        bbox_to_anchor=(x, y), loc='center',        ncol=2, fontsize=15,        handlelength=1, handleheight=0.4,        edgecolor=background_color    )

次に、前のステップで format_tricks() と同様にこの関数を追加します。

fig = plt.figure(figsize=(10, 8))ax = create_age_distribution(    female_df=population_female,    male_df=population_male,    country="World",    year="2021")# New functionsformat_ticks(ax, xformat="millions")add_legend(x=0.5, y=1.09)plt.title("Age Distribution for the World in 2021", y=1.14, fontsize=20)plt.tight_layout()plt.show()

私はplt.title()を追加してタイトルを追加しました。

全てを実行すると、年齢分布グラフは次のようになります。

Graph created by the author

素晴らしいですね。次に進みましょう。

PILイメージの作成と余白の追加

いずれかの時点で、グラフをディスクに保存して他の方法でカスタマイズするために、図を画像に変換したいと思う場合があります。

そのようなカスタマイズの1つは、グラフの周りに余白を追加して、より詰まった印象を和らげることです。

最初に、Matplotlibの図をPILイメージに変換するcreate_image_from_figure()関数を作成しました。

def create_image_from_figure(fig):    plt.tight_layout()        fig.canvas.draw()    data = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)    data = data.reshape((fig.canvas.get_width_height()[::-1]) + (3,))    plt.close()         return Image.fromarray(data)

そして、余白を追加する関数を以下に示します。

def add_padding_to_chart(chart, left, top, right, bottom, background):    size = chart.size    image = Image.new("RGB", (size[0] + left + right, size[1] + top + bottom), background)    image.paste(chart, (left, top))    return image

再び、グラフを作成する元のコードにこれらの関数を追加しました。以下のようになります。

fig = plt.figure(figsize=(10, 8))ax = create_age_distribution(    female_df=population_female,    male_df=population_male,    country="World",    year="2021")# New functionsformat_ticks(ax, xformat="millions")add_legend(x=0.5, y=1.09)plt.title("Age Distribution for the World in 2021", y=1.14, fontsize=20)image = create_image_from_figure(fig)image = add_padding_to_chart(image, 20, 20, 20, 5, background_color)

そして、以下が生成されたグラフです。

Graph created by the author

私の目には、これは完璧に近いように見えます。もう2つ、グリッドの作成とタイムラプスの可視化方法を紹介したいと思います。

最初に、グリッドの作成方法から始めましょう。

複数の国を含むグリッドの作成

plt.subplots()を使用してグリッドを作成できますが、このチュートリアルでは、グリッドの画像を作成して見栄えを良くすることをお勧めします。

次の関数は、画像のリストを受け取り、ncolsでグリッドを作成します。すべての図が収まるだけの十分な大きさの単一の背景色の空の画像を作成することで機能します。

def create_grid(figures, pad, ncols):    nrows = int(len(figures) / ncols)    size = figures[0].size    image = Image.new(        "RGBA",        (ncols * size[0] + (ncols - 1) * pad, nrows * size[1] + (nrows - 1) * pad),        "#ffffff00"    )    for i, figure in enumerate(figures):        col, row = i % ncols, i // ncols        image.paste(figure, (col * (size[0] + pad), row * (size[1] + pad)))    return image

以下のコードでは、国のリストを反復処理し、生成されたグラフをfiguresに追加し、最後にcreate_grid()を実行してグリッドを作成しています。

figures = []for country in [    "United States", "China", "Japan", "Brazil", "Canada",    "Germany", "Pakistan", "Russian Federation", "Nigeria",     "Sweden", "Cambodia", "Saudi Arabia", "Iceland",    "Spain", "South Africa", "Morocco"]:    fig = plt.figure(figsize=(10, 8))    ax = create_age_distribution(        female_df=population_ratio_female,        male_df=population_ratio_male,        country=country,        year="2021"    )        ax.set(xlim=(-10, 10))    # New functions    format_ticks(ax, xformat="percentage")    add_legend(x=0.5, y=1.09)    plt.title("Age Distribution for {} in 2021".format(country), y=1.14, fontsize=20)    image = create_image_from_figure(fig)    image = add_padding_to_chart(image, 20, 20, 20, 5, background_color)        figures.append(image)    grid = create_grid(figures, pad=20, ncols=4)

注意点として、絶対数ではなく比率を使用し、xlim=(-10, 10)を設定しています。そうしないと、国々を視覚的に比較できなくなってしまいます。

Graphs created by the author

このチュートリアルの最後の部分に移りましょう。時間経過に伴う視覚化の作成方法について説明します。

時間経過の視覚化の作成

静的な年齢分布グラフは見栄えが良いですが、時間の経過に伴う変化を見るのは魅力的です。

1960年から2021年までの実際の値と、2050年までの予測値があるため、比較的長い期間のタイムラプスアニメーションを作成できます。

始める前に、私が使用しているPT Monoフォントは、すべての文字の高さが同じではないことを知っておく必要があります。視覚化を良く見せるために、plt.title()ではなくplt.text()を年に使用する必要があります。他のフォントを使用する場合は必要ありません。

以下はコードです。

images = []years = list(population_male.columns[4:])for year in years:    fig = plt.figure(figsize=(10, 8))    ax = create_age_distribution(        female_df=population_female,        male_df=population_male,        country="World",        year=year    )    # 新しい関数    format_ticks(ax, xformat="millions", xlim=(-400000000, 400000000))    add_legend(x=0.5, y=1.09)    plt.title("Age Distribution for the World in      ", y=1.14, fontsize=21)    plt.text(x=0.77, y=1.15, s=str(year), fontsize=21, transform=ax.transAxes)    image = create_image_from_figure(fig)    image = add_padding_to_chart(image, 20, 20, 20, 5, background_color)    images.append(image)

私はimageioを使用して、画像のリストからGIFを作成しています。

# Duplicating the last frames to add a delay # before the animation restartsimages = images + [images[-1] for _ in range(20)]imageio.mimwrite('./time-lapse.gif', images, duration=0.2)

結果を見てみましょう。

Visualization created by the author

素晴らしい!これでこのチュートリアルは終了です。役に立ったかどうか、教えてください。

結論

このチュートリアルを書くのは楽しかったです。年齢分布は国の人口統計の素晴らしい視覚化方法であり、それらを目立たせるためのいくつかの方法を見ました。

スタイル、グリッド、アニメーションの作成方法を学びました。ここで行ったように、関数を書くことは、異なるアイデアやスタイルを素早くテストするのにも役立ちます。

将来に活用できる何かを学んでいただけたら幸いです。

私のチュートリアルを読んでいただきありがとうございました。このようなコンテンツを楽しんでいただけるかどうか、お知らせください。

人々が望む場合は、もっとチュートリアルを作成できます!:)

また次回お会いしましょう。

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more