カテゴリー

発展系

【python&3Dピラミッド】plotlyで3次元ピラミッドを作成

2021年11月18日

こんな人にオススメ

*」を使った2次元の簡易的なピラミッドの作成方法は結構出ているけど、3次元のピラミッドの作成方法はあまり見つからない。

もし作成したいならどういうふうに作成できる?

ということで、今回はplotlyを使用して3次元のピラミッドを作成してみる。pythonでピラミッドを検索すると大抵は「*」を使用した初心者用コードが引っかかる。

でもそれはあくまでも2次元であり、forなどの練習用のピラミッド。今回は見た目を完全にピラミッドに似せるように作成する。

なお、今回は数式などを使用せずにサクッとしたピラミッドとする。もちろん数式から作成してもいいけど、数式を解読するのが面倒なのでパス。

python環境は以下。

  • Python 3.9.7
  • numpy 1.21.3
  • plotly 5.3.1
  • plotly-orca 3.4.2
スポンサーリンク
スポンサーリンク

運営者のメガネです。TwitterInstagramも運営してます。

自己紹介はこちらから、お問い合わせはこちら。

運営者メガネ

作成したコード全文

下準備

import numpy as np
import plotly.graph_objects as go
import plotly.io as pio

まずは下準備としてのimport関連。今回はplotlyのSurfaceプロットでグラフ化する。しかし、plotly.express、すなわちpxではSurfaceプロットがないのでgoを使用。

Surfaceにすることで滑らかな面としてプロットすることが可能。もちろん散布図としてプロットすることも可能。plotlyの3次元グラフについては以下参照。

【plotly&3D】goで3Dグラフを作成

続きを見る

【plotly&3D】px.scatter_3dとpx.line_3Dで3Dグラフを作成

続きを見る

グラフ表示・保存用の関数を定義

# グラフ保存用の関数
def show_save(fig, config, save_name):
    fig.show()
    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")

plotlyのグラフを表示・グラフを保存するための関数を予め定義しておく。この関数を使用するだけで簡単にグラフの表示と保存が可能。

関数を予め定義しておくことで、処理の一括管理が可能だったり複数箇所で同じコードを書く時に関数だけ書けば良くなる。関数については以下参照。

【plotly&工夫】楽にグラフを描くためのplotlyの関数化

続きを見る

お試しで作成


まずはお試しでピラミッドを作成。plotlyで3次元Surfaceプロットをするには1次元のx, yと2次元のzの配列が必要。

今回はシンプルにベースをz=0にして1段ごとに+1されるようなピラミッドを作成。上のグラフだと3段。

あとはx, y, zgo.Surfaceに入れ、ちょっとだけ透明にしてグラフを作成した。

x = (-2, -1, 0, 1, 2)
y = (-2, -1, 0, 1, 2)
z = np.array(
    [
        [0, 0, 0, 0, 0],
        [0, 1, 1, 1, 0],
        [0, 1, 2, 1, 0],
        [0, 1, 1, 1, 0],
        [0, 0, 0, 0, 0],
    ]
)

d = go.Surface(
    x=x, y=y, z=z,
    opacity=0.9,  # 透明度
)
fig = go.Figure(data=[d])
save_name = 'plotly-3d-pyramid_yramid_trial'
show_save(fig=fig, config=None, save_name=save_name)

任意の段数のピラミッド配列を作成

先程のグラフを土台としてあとはデータ数を増やせばより滑らかなピラミッドになるけど、さっきのようにいちいちzの2次元配列を作成するのは面倒。

ということで、辺の長さを指定すると自動でピラミッドの配列を作成できるような仕組みを作る。作成手順は以下。

  1. ピラミッドの頂点を含む1/4の部分をベースとする
  2. このベースを1行ずつ作成する(STEP1)
  3. ベースを行反転させて頂点部分の1行を削除した配列を下に結合(STEP2)
  4. 結合後の配列を列反転させて頂点部分の1列を削除した配列を右に結合(STEP3)
  5. 完成

STEP1がかなり面倒になるけどそこを乗り切れば結合はすぐにできる。

ベースの配列を作成

length = 5  # ピラミッドの1辺の長さ
nums = length // 2 + 1  # ピラミッドの値の種類(長さが偶数の時は辺+1したピラミッドを作成)

rows = []  # ベースとなる配列の各行の値を入れる2次元配列
for row in range(nums):  # 1行ごとに見る
    n = 0  # 各行の各列の値
    cols = []  # row行目の値を入れる
    for col in range(nums):  # row行目の各列
        cols.append(n)  # 一番左は0
        if row:  # 0行目の時はここはFalse、それ以外の行目ならTrue
            n += 1  # 各列の値は1ずつ増えていく
            row -= 1  # あと何回、列の値が変わるかに相当
    rows.append(cols)  # row行目の配列を格納

ピラミッドには頂点が必要なので、頂点ができない辺の長さが偶数の場合は変数numsで自動で奇数になるように調節している。lengthを4にするとnumsは3となる。

初めの0行目は全て0で、次の行では1列目で+1、2行目では1, 2列目でそれぞれ+1されるので各行rowが0かそれ以外かをifで判断しつつ列の値nを増やしている。

それと同時に1行目では2列目は1のままなのに対して2行目では2列目は+1されて2となるので、rowを無理矢理減らして1行ずつピラミッド構造になるようにした。

これでSTEP1のベースとなる配列は完成。

ベースを結合

arr = np.array(rows)  # 2次元配列の変換
print(arr, '\\n')
# [[0 0 0]
#  [0 1 1]
#  [0 1 2]]

ベースとなる配列ができたので、これを結合していく。STEP2ではベースの下に配列を結合するので下用の配列を作成する。

配列を[::-1]で行方向に反転し、頂点を含む0行目は使用しないように[1:]で1行目以降を抽出した。

あとはベースの下に結合すればいい。今回はnumpynp.vstackで結合した。

bottom = arr[::-1][1:]  # ベース下用の配列
print(bottom, '\\n')
# [[0 1 1]
#  [0 0 0]]

arr2 = np.vstack([arr, bottom])  # ベース下に配列を結合
print(arr2, '\\n')
# [[0 0 0]
#  [0 1 1]
#  [0 1 2]
#  [0 1 1]
#  [0 0 0]]

配列の右に結合するときも同じようにできる。今度は左右方向なので[:, ::-1]で列の反転をする。また、左右方向への結合はnp.hstackを使用した。

left = arr2[:, ::-1][:, 1:]  # ベース+下の右用の配列
print(left, '\\n')
# [[0 0]
#  [1 0]
#  [1 0]
#  [1 0]
#  [0 0]]

arr3 = np.hstack([arr2, left])  # ベース+下の右に配列を結合
print(arr3, '\\n')
# [[0 0 0 0 0]
#  [0 1 1 1 0]
#  [0 1 2 1 0]
#  [0 1 1 1 0]
#  [0 0 0 0 0]]

これでピラミッドが完成する。

関数化して辺の値だけでピラミッド作成

def make_arr(length):
    # ピラミッドの値の種類(長さが偶数の時は辺+1したピラミッドを作成)
    nums = length // 2 + 1

    rows = []  # ベースとなる配列の各行の値を入れる2次元配列
    for row in range(nums):  # 1行ごとに見る
        n = 0  # 各行の各列の値
        cols = []  # row行目の値を入れる
        for col in range(nums):  # row行目の各列
            cols.append(n)  # 一番左は0
            if row:  # 0行目の時はここはFalse、それ以外の行目ならTrue
                n += 1  # 各列の値は1ずつ増えていく
                row -= 1  # あと何回、列の値が変わるかに相当
        rows.append(cols)  # row行目の配列を格納

    arr = np.array(rows)  # 2次元配列の変換
    bottom = arr[::-1][1:]  # ベース下用の配列
    arr2 = np.vstack([arr, bottom])  # ベース下に配列を結合
    left = arr2[:, ::-1][:, 1:]  # ベース+下の左用の配列
    arr3 = np.hstack([arr2, left])  # ベース+下の左に配列を結合

    return arr3

さっきの処理をいちいち書いてもいいけどこれは面倒。ということで、作成したいピラミッドの辺の値を入力するだけで自動でピラミッド配列を作成してくれる関数を作成。

中身はさっきの処理をそのまま書いた。関数化することで以下のように複数回使用するときにスッキリした見た目になる。

pyramid = make_arr(length=5)
print(pyramid)
# [[0 0 0 0 0]
#  [0 1 1 1 0]
#  [0 1 2 1 0]
#  [0 1 1 1 0]
#  [0 0 0 0 0]]

pyramid = make_arr(length=9)
print(pyramid)
# [[0 0 0 0 0 0 0 0 0]
#  [0 1 1 1 1 1 1 1 0]
#  [0 1 2 2 2 2 2 1 0]
#  [0 1 2 3 3 3 2 1 0]
#  [0 1 2 3 4 3 2 1 0]
#  [0 1 2 3 3 3 2 1 0]
#  [0 1 2 2 2 2 2 1 0]
#  [0 1 1 1 1 1 1 1 0]
#  [0 0 0 0 0 0 0 0 0]]

3Dピラミッドをグラフ化


ということで、最大の難関であったピラミッドの配列をクリアできたのでグラフ化する。なお、横軸・縦軸の配列であるx, yはピラミッドの辺の長さからその半分の値を引いて中心を0となるように設定。

また、レイアウトもタイトルの位置やカラーバーの位置を調節した。色々設定できるけど、ややこしくなるのでシンプルにしておいた。

なお、ピラミッドの辺の数は101とした。かなり滑らか。

def make_pyramid(length):
    pyramid = make_arr(length=length)  # ピラミッド作成

    base = np.array(range(len(pyramid)))  # ベースとなる配列
    x = base - length // 2  # 配列の真ん中を0にする
    y = base - length // 2  # yも同様

    # ピラミッドをプロット
    d = go.Surface(
        x=x, y=y, z=pyramid,
        opacity=0.9,
        colorbar=dict(x=0.8)
    )

    # グラフ化
    fig = go.Figure(
        data=[d],
        # レイアウトの調節
        layout=go.Layout(
            title=dict(text=f"length={length}", x=0.5, xanchor='center'),
        ),
    )

    save_name = f"plotly-3d-pyramid_pyramid{length}"
    show_save(fig=fig, config=None, save_name=save_name)

make_pyramid(length=101)

数式がなくてもできる

今回はplotlyを使って3次元のピラミッドをグラフ化する方法を解説した。もちろん今回の方法以外にもアプローチの仕方は多くあるだろう。

しかし、わざわざ数式をイジって作成しなくてもある程度のものができる。数式をイジるとなるとかなりハードルが上がるけど配列を作るだけなら簡単。

このピラミッドがどこで使用できるかは謎だけどこのノウハウはどこかで役立つかもしれない。

関連記事

【plotly&コッホ曲線】フラクタル図形の雪の結晶をスライダーで作成してみる

続きを見る

【plotly&Scattergeo】世界地図に各国の首都の位置をプロット

続きを見る

【plotly&円グラフ】世界のスマホ出荷台数をgo.Pieで円グラフ化

続きを見る

関連コンテンツ

スポンサーリンク

Amazonのお買い物で損したない人へ

1回のチャージ金額通常会員プライム会員
¥90,000〜2.0%2.5%
¥40,000〜1.5%2.0%
¥20,000〜1.0%1.5%
¥5,000〜0.5%1.0%

Amazonギフト券にチャージすることでお得にお買い物できる。通常のAmazon会員なら最大2.0%、プライム会員なら2.5%還元なのでバカにならない。

ゲットしたポイントは通常のAmazonでのお買い物に使えるからお得だ。一度チャージしてしまえば、好きなタイミングでお買いものできる。

なお、有効期限は10年だから安心だ。いつでも気軽にAmazonでお買い物できる。

Amazonチャージはここから出来るで

もっとお得なAmazon Prime会員はこちらから

30日間無料登録

執筆者も便利に使わせてもらってる

スポンサーリンク

  • この記事を書いた人

メガネ

独学でpythonを学び天文学系の大学院を修了。 ガジェット好きでMac×Android使い。色んなスマホやイヤホンを購入したいけどお金がなさすぎて困窮中。 元々、人見知りで根暗だったけど、人生楽しもうと思って良い方向に狂ったために今も人生めちゃくちゃ楽しい。 pythonとガジェットをメインにブログを書いていますので、興味を持たれましたらちょこちょこ訪問してくだされば幸いです🥰。 自己紹介→変わって楽しいの繰り返し

-発展系
-, ,