こんな人にオススメ
「*
」を使った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
作成したコード全文
下準備
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
, z
をgo.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/4の部分をベースとする
- このベースを1行ずつ作成する(STEP1)
- ベースを行反転させて頂点部分の1行を削除した配列を下に結合(STEP2)
- 結合後の配列を列反転させて頂点部分の1列を削除した配列を右に結合(STEP3)
- 完成
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行目以降を抽出した。
あとはベースの下に結合すればいい。今回はnumpy
のnp.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で円グラフ化
続きを見る