カテゴリー

Pythonの発展系

【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

運営者のメガネとです。YouTubeTwitterInstagramも運営中。

自己紹介はこちらから、お問い合わせはこちらからお願いいたします。

運営者メガネ

作成したコード全文

下準備

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グラフを作成

こんな人にオススメ plotyで3Dプ ...

続きを見る

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

こんな人にオススメ plotlyのpxで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でグ} ...

続きを見る

お試しで作成


まずはお試しでピラミッドを作成。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で円グラフ化

こんな人にオススメ 日本っ ...

続きを見る

スイッチボット

2022/11/28

【SwitchBotロックレビュー】これからのスタンダードになりうるスマートロック

こんな人にオススメ SwitchBotからスマートロック「SwitchBotロック」が発売された ...

生活に役立つ

2022/11/28

【メガネ厳選】クソ便利に使っているサービスやアイテム達

このページでは執筆者「メガネ」が実際に使って便利だと感じているサ ...

マウス

2022/9/11

【Logicool MX ERGO vs MX Master 3】ERGOをメインにした決定的な理由

こんな疑問・お悩みを持っている人におすすめ 執筆者はLogicoolのハイエンӠ ...

完全ワイヤレスイヤホン(TWS)

2022/11/21

【ながら聴きイヤホン比較】SONY LinkBuds、ambie、BoCoはどれがおすすめ?

こんな人におすすめ 耳を塞がない開放型のイヤホンに完全ワイヤレスӟ ...

macOSアプリケーション

2022/10/15

【M1 Mac】MacBook Proに入れている便利でニッチなアプリを21個紹介する

こんな人におすすめ MacBookを購入してLINEとか必要最低限のアプリは入れた。 ...

完全ワイヤレスイヤホン(TWS)

2022/10/23

【SENNHEISER MOMENTUM True Wireless 3レビュー】高レベルでバランス型の高音質イヤホン

こんな人におすすめ SENNHEISER MOMENTUM True Wireless 3って実際のところどうなの? 評判は良い ...

完全ワイヤレスイヤホン(TWS)

2022/11/21

【SONY WF-1000XM4レビュー】神とゴミのハーフ&ハーフ

こんな人におすすめ SONYのフラグシップモデル「SONY WF-1000XM4」ってどれくらい性 ...

完全ワイヤレスイヤホン(TWS)

2022/8/19

【Nothing ear (1)レビュー】ライトな完成度、アップデートに期待

こんな人にオススメ 完全ワイヤレスイヤホン(TWS)でスケルトンボディ ...

Pythonを学びたいけど独学できる時間なんてない人へのすゝめ

執筆者は大学の研究室・大学院にて独学でPythonを習得した。

でも社会人になったら独学で行うには時間も体力もなくて大変だ。

時間がない社会人だからこそプロの教えを乞うのが効率的。

ここでは色んなタイプに合ったプログラミングスクールの紹介をする。

  • この記事を書いた人

メガネ

ベンチャー企業のWebエンジニア駆け出し。独学のPythonで天文学系の大学院を修了→新卒を1.5年で辞める→転職→今に至る。
常時金欠のガジェット好きでM1 MacBook Pro x Galaxy S22 Ultraの狂人。
人見知りで根暗だったけど、人生楽しもうと思って良い方向に狂う→人生が楽しい

ガジェットのレビューとPythonコードを記事にしています。ぜひ楽しんでください🦊
自己紹介と半生→変わって楽しいの繰り返し

-Pythonの発展系
-, ,