.ipynb

3D Elliptic Fourier Analysis#

import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px

from sklearn.decomposition import PCA

from ktch.datasets import load_outline_leaf_bending
from ktch.harmonic import EllipticFourierAnalysis
from ktch.plot import explained_variance_ratio_plot

Load the leaf bending outline dataset#

data = load_outline_leaf_bending(as_frame=True)
data.coords
x y z
specimen_id coord_id
1 0 0.000000 0.000000 0.000000
1 0.000000 -0.046295 0.000000
2 0.000000 -0.092589 0.000000
3 0.000630 -0.095962 0.045238
4 0.002576 -0.098305 0.091431
... ... ... ... ...
60 195 0.000000 0.148530 0.000000
196 0.000000 0.118824 0.000000
197 0.000000 0.089118 0.000000
198 0.000000 0.059412 0.000000
199 0.000000 0.029706 0.000000

12000 rows × 3 columns

df_meta = data.meta.copy()
df_meta["bending_angle"] = (
    np.degrees(df_meta["alphaB"]).round().astype(int).astype(str) + "\u00b0"
)
df_meta["aspect_ratio"] = df_meta["alpha"]
df_meta
lmax c alpha A alphaB kB alpha_random alphaB_random bending_angle aspect_ratio
specimen_id
1 0.4 0.65 0.08 1.0 2.443461 0.2 0.060572 2.528513 140° 0.08
2 0.4 0.65 0.08 1.0 2.443461 0.2 0.090911 2.318855 140° 0.08
3 0.4 0.65 0.08 1.0 2.443461 0.2 0.088957 2.417189 140° 0.08
4 0.4 0.65 0.08 1.0 2.443461 0.2 0.074596 2.506030 140° 0.08
5 0.4 0.65 0.08 1.0 2.443461 0.2 0.098990 2.294188 140° 0.08
6 0.4 0.65 0.08 1.0 2.443461 0.2 0.073196 2.492090 140° 0.08
7 0.4 0.65 0.08 1.0 2.443461 0.2 0.077120 2.287193 140° 0.08
8 0.4 0.65 0.08 1.0 2.443461 0.2 0.083971 2.280600 140° 0.08
9 0.4 0.65 0.08 1.0 2.443461 0.2 0.073029 2.431389 140° 0.08
10 0.4 0.65 0.08 1.0 2.443461 0.2 0.099243 2.553002 140° 0.08
11 0.4 0.65 0.08 1.0 1.396263 0.2 0.089302 1.409695 80° 0.08
12 0.4 0.65 0.08 1.0 1.396263 0.2 0.076330 1.242604 80° 0.08
13 0.4 0.65 0.08 1.0 1.396263 0.2 0.060501 1.299788 80° 0.08
14 0.4 0.65 0.08 1.0 1.396263 0.2 0.087227 1.536335 80° 0.08
15 0.4 0.65 0.08 1.0 1.396263 0.2 0.098797 1.548685 80° 0.08
16 0.4 0.65 0.08 1.0 1.396263 0.2 0.076723 1.280753 80° 0.08
17 0.4 0.65 0.08 1.0 1.396263 0.2 0.093675 1.558039 80° 0.08
18 0.4 0.65 0.08 1.0 1.396263 0.2 0.095911 1.566630 80° 0.08
19 0.4 0.65 0.08 1.0 1.396263 0.2 0.078236 1.387295 80° 0.08
20 0.4 0.65 0.08 1.0 1.396263 0.2 0.095689 1.447270 80° 0.08
21 0.4 0.65 0.08 1.0 0.349066 0.2 0.082873 0.390205 20° 0.08
22 0.4 0.65 0.08 1.0 0.349066 0.2 0.064031 0.266366 20° 0.08
23 0.4 0.65 0.08 1.0 0.349066 0.2 0.089630 0.373168 20° 0.08
24 0.4 0.65 0.08 1.0 0.349066 0.2 0.078860 0.330521 20° 0.08
25 0.4 0.65 0.08 1.0 0.349066 0.2 0.087673 0.318159 20° 0.08
26 0.4 0.65 0.08 1.0 0.349066 0.2 0.088925 0.305914 20° 0.08
27 0.4 0.65 0.08 1.0 0.349066 0.2 0.077160 0.372707 20° 0.08
28 0.4 0.65 0.08 1.0 0.349066 0.2 0.083355 0.268017 20° 0.08
29 0.4 0.65 0.08 1.0 0.349066 0.2 0.083693 0.406805 20° 0.08
30 0.4 0.65 0.08 1.0 0.349066 0.2 0.081946 0.311726 20° 0.08
31 0.4 0.65 0.16 1.0 2.443461 0.2 0.173997 2.326146 140° 0.16
32 0.4 0.65 0.16 1.0 2.443461 0.2 0.178168 2.367912 140° 0.16
33 0.4 0.65 0.16 1.0 2.443461 0.2 0.166143 2.497124 140° 0.16
34 0.4 0.65 0.16 1.0 2.443461 0.2 0.142324 2.521001 140° 0.16
35 0.4 0.65 0.16 1.0 2.443461 0.2 0.159544 2.492420 140° 0.16
36 0.4 0.65 0.16 1.0 2.443461 0.2 0.176739 2.296786 140° 0.16
37 0.4 0.65 0.16 1.0 2.443461 0.2 0.170455 2.304695 140° 0.16
38 0.4 0.65 0.16 1.0 2.443461 0.2 0.148710 2.478740 140° 0.16
39 0.4 0.65 0.16 1.0 2.443461 0.2 0.141903 2.555287 140° 0.16
40 0.4 0.65 0.16 1.0 2.443461 0.2 0.171313 2.610918 140° 0.16
41 0.4 0.65 0.16 1.0 1.396263 0.2 0.176521 1.244129 80° 0.16
42 0.4 0.65 0.16 1.0 1.396263 0.2 0.167533 1.252522 80° 0.16
43 0.4 0.65 0.16 1.0 1.396263 0.2 0.175097 1.438110 80° 0.16
44 0.4 0.65 0.16 1.0 1.396263 0.2 0.146855 1.570428 80° 0.16
45 0.4 0.65 0.16 1.0 1.396263 0.2 0.143005 1.422087 80° 0.16
46 0.4 0.65 0.16 1.0 1.396263 0.2 0.174511 1.497144 80° 0.16
47 0.4 0.65 0.16 1.0 1.396263 0.2 0.170496 1.470243 80° 0.16
48 0.4 0.65 0.16 1.0 1.396263 0.2 0.151212 1.282264 80° 0.16
49 0.4 0.65 0.16 1.0 1.396263 0.2 0.176779 1.257857 80° 0.16
50 0.4 0.65 0.16 1.0 1.396263 0.2 0.157964 1.505475 80° 0.16
51 0.4 0.65 0.16 1.0 0.349066 0.2 0.166466 0.346456 20° 0.16
52 0.4 0.65 0.16 1.0 0.349066 0.2 0.177635 0.386719 20° 0.16
53 0.4 0.65 0.16 1.0 0.349066 0.2 0.155776 0.441191 20° 0.16
54 0.4 0.65 0.16 1.0 0.349066 0.2 0.175787 0.407493 20° 0.16
55 0.4 0.65 0.16 1.0 0.349066 0.2 0.146865 0.504861 20° 0.16
56 0.4 0.65 0.16 1.0 0.349066 0.2 0.163440 0.445301 20° 0.16
57 0.4 0.65 0.16 1.0 0.349066 0.2 0.160243 0.366476 20° 0.16
58 0.4 0.65 0.16 1.0 0.349066 0.2 0.158335 0.354346 20° 0.16
59 0.4 0.65 0.16 1.0 0.349066 0.2 0.148304 0.235357 20° 0.16
60 0.4 0.65 0.16 1.0 0.349066 0.2 0.160054 0.206858 20° 0.16
coords = data.coords.to_numpy().reshape(-1, 200, 3)
coords.shape
(60, 200, 3)

Visualize 3D outlines#

representative_ids = [0, 10, 20, 30, 40, 50]
bending_order = [ str(deg)+ "\u00b0" for deg in  [20, 80, 140] ]

dfs = []
for sid in representative_ids:
    df = pd.DataFrame(coords[sid], columns=["x", "y", "z"])
    df["idx"] = data.meta.index[sid]
    df["bending_angle"] = df_meta.iloc[sid]["bending_angle"]
    dfs.append(df)
df_vis = pd.concat(dfs, ignore_index=True)

fig = px.line_3d(
    df_vis,
    x="x",
    y="y",
    z="z",
    line_group="idx",
    color="bending_angle",
    category_orders ={"bending_angle":bending_order},
    color_discrete_sequence=px.colors.qualitative.Set2,
)
fig.update_layout(scene=dict(aspectmode="data"))
fig.show()

3D EFA without normalization#

efa = EllipticFourierAnalysis(n_harmonics=20, n_dim=3)
coef = efa.fit_transform(coords, norm=False)
coef.shape
(60, 126)

Reconstruction from coefficients#

coords_recon = efa.inverse_transform(coef, t_num=200, norm=False)
sid = 0
df_orig = pd.DataFrame(coords[sid], columns=["x", "y", "z"])
df_orig["type"] = "original"
df_rec = pd.DataFrame(coords_recon[sid], columns=["x", "y", "z"])
df_rec["type"] = "reconstructed"
df_cmp = pd.concat([df_orig, df_rec], ignore_index=True)

fig = px.line_3d(df_cmp, x="x", y="y", z="z", color="type")
fig.update_layout(scene=dict(aspectmode="data"))
fig.show()

3D EFA#

efa = EllipticFourierAnalysis(n_harmonics=20, n_dim=3)
coef = efa.fit_transform(coords, norm=True)
coef.shape
(60, 126)

Reconstruction from coefficients#

coords_recon = efa.inverse_transform(coef, t_num=200, norm=True)
representative_ids = [0, 10, 20, 30, 40, 50]

dfs = []
for sid in representative_ids:
    df = pd.DataFrame(coords_recon[sid], columns=["x", "y", "z"])
    df["idx"] = data.meta.index[sid]
    df["bending_angle"] = df_meta.iloc[sid]["bending_angle"]
    dfs.append(df)
df_vis = pd.concat(dfs, ignore_index=True)

fig = px.line_3d(
    df_vis, x="x", y="y", z="z",
    line_group="idx",
    color="bending_angle",
    category_orders ={"bending_angle":bending_order},
    color_discrete_sequence=px.colors.qualitative.Set2
    )
fig.update_layout(scene=dict(aspectmode="data"))
fig.show()

PCA#

pca = PCA(n_components=12)
pcscores = pca.fit_transform(coef)
df_pca = pd.DataFrame(pcscores, columns=[f"PC{i+1}" for i in range(12)])
df_pca.index = df_meta.index
df_pca = df_pca.join(df_meta)
df_pca
PC1 PC2 PC3 PC4 PC5 PC6 PC7 PC8 PC9 PC10 ... lmax c alpha A alphaB kB alpha_random alphaB_random bending_angle aspect_ratio
specimen_id
1 -1.212191 1.121579 0.107171 0.011560 0.012842 0.001138 -0.002117 -0.000665 5.195792e-04 0.000332 ... 0.4 0.65 0.08 1.0 2.443461 0.2 0.060572 2.528513 140° 0.08
2 -0.936970 0.436713 0.028150 0.005296 -0.004628 0.001218 -0.000426 -0.000119 -3.270928e-04 -0.000379 ... 0.4 0.65 0.08 1.0 2.443461 0.2 0.090911 2.318855 140° 0.08
3 -1.035797 0.439970 0.047183 0.004650 -0.004887 0.000129 -0.000774 -0.000030 -2.139763e-04 -0.000394 ... 0.4 0.65 0.08 1.0 2.443461 0.2 0.088957 2.417189 140° 0.08
4 -1.145406 0.728617 0.075460 0.008020 0.000725 -0.001096 -0.000554 0.000422 2.015761e-04 -0.000002 ... 0.4 0.65 0.08 1.0 2.443461 0.2 0.074596 2.506030 140° 0.08
5 -0.912451 0.301951 0.016461 0.004460 -0.006107 0.000491 0.000658 0.000267 -4.775718e-04 0.000075 ... 0.4 0.65 0.08 1.0 2.443461 0.2 0.098990 2.294188 140° 0.08
6 -1.133819 0.771139 0.073925 0.008667 0.001817 -0.000782 -0.000424 0.000412 2.093879e-04 0.000050 ... 0.4 0.65 0.08 1.0 2.443461 0.2 0.073196 2.492090 140° 0.08
7 -0.906528 0.758235 0.025590 0.009584 0.001321 0.000946 0.001348 0.000506 -1.056459e-04 0.000153 ... 0.4 0.65 0.08 1.0 2.443461 0.2 0.077120 2.287193 140° 0.08
8 -0.897786 0.598010 0.021691 0.007696 -0.002021 0.001004 0.000776 0.000309 -2.412954e-04 -0.000039 ... 0.4 0.65 0.08 1.0 2.443461 0.2 0.083971 2.280600 140° 0.08
9 -1.069214 0.803718 0.060063 0.009552 0.002635 -0.000024 0.000220 0.000442 1.188405e-04 0.000132 ... 0.4 0.65 0.08 1.0 2.443461 0.2 0.073029 2.431389 140° 0.08
10 -1.153353 0.201580 0.064673 0.000142 -0.008252 -0.002337 -0.001004 0.000056 -2.327827e-04 -0.000266 ... 0.4 0.65 0.08 1.0 2.443461 0.2 0.099243 2.553002 140° 0.08
11 0.098271 0.584667 -0.070353 0.000910 -0.003291 0.000134 -0.000159 -0.000284 1.208559e-04 -0.000146 ... 0.4 0.65 0.08 1.0 1.396263 0.2 0.089302 1.409695 80° 0.08
12 0.409715 0.866223 -0.078336 -0.004074 0.001146 -0.002104 0.000748 0.000136 2.644008e-04 -0.000073 ... 0.4 0.65 0.08 1.0 1.396263 0.2 0.076330 1.242604 80° 0.08
13 0.479149 1.371337 -0.078663 -0.005317 0.015571 -0.000932 0.000856 -0.001264 -3.552821e-04 -0.000120 ... 0.4 0.65 0.08 1.0 1.396263 0.2 0.060501 1.299788 80° 0.08
14 -0.043950 0.641566 -0.067710 0.002521 -0.002334 0.000573 0.000627 -0.000120 2.669955e-05 -0.000117 ... 0.4 0.65 0.08 1.0 1.396263 0.2 0.087227 1.536335 80° 0.08
15 -0.113632 0.414991 -0.066985 0.002730 -0.005277 0.000412 0.000743 0.000109 -3.860665e-05 0.000190 ... 0.4 0.65 0.08 1.0 1.396263 0.2 0.098797 1.548685 80° 0.08
16 0.355298 0.864501 -0.078721 -0.003226 0.001113 -0.001853 0.000901 0.000137 2.508130e-04 -0.000074 ... 0.4 0.65 0.08 1.0 1.396263 0.2 0.076723 1.280753 80° 0.08
17 -0.102059 0.508699 -0.065106 0.002932 -0.004082 0.000865 0.000324 -0.000143 -2.991154e-05 -0.000056 ... 0.4 0.65 0.08 1.0 1.396263 0.2 0.093675 1.558039 80° 0.08
18 -0.121821 0.467478 -0.065432 0.002982 -0.004627 0.000701 0.000571 -0.000006 -4.302129e-05 0.000058 ... 0.4 0.65 0.08 1.0 1.396263 0.2 0.095911 1.566630 80° 0.08
19 0.202180 0.844041 -0.077693 -0.000848 0.000793 -0.001103 0.001261 0.000145 1.984502e-04 -0.000068 ... 0.4 0.65 0.08 1.0 1.396263 0.2 0.078236 1.387295 80° 0.08
20 0.018057 0.463559 -0.069791 0.001618 -0.004887 0.000130 0.000180 -0.000058 8.152559e-05 0.000060 ... 0.4 0.65 0.08 1.0 1.396263 0.2 0.095689 1.447270 80° 0.08
21 1.395888 0.273970 0.029565 -0.007117 -0.002888 -0.000400 -0.000432 0.000188 2.413662e-04 0.000073 ... 0.4 0.65 0.08 1.0 0.349066 0.2 0.082873 0.390205 20° 0.08
22 1.900065 0.629052 0.074601 -0.016125 0.008819 0.000725 -0.000925 0.000235 -1.295535e-03 0.000062 ... 0.4 0.65 0.08 1.0 0.349066 0.2 0.064031 0.266366 20° 0.08
23 1.312209 0.126744 0.033607 -0.002834 -0.004087 0.000589 -0.000661 -0.000273 3.846086e-04 -0.000025 ... 0.4 0.65 0.08 1.0 0.349066 0.2 0.089630 0.373168 20° 0.08
24 1.529104 0.313506 0.044290 -0.008627 -0.001265 -0.000009 -0.000039 0.000378 2.528159e-05 0.000031 ... 0.4 0.65 0.08 1.0 0.349066 0.2 0.078860 0.330521 20° 0.08
25 1.399288 0.122454 0.045810 -0.003021 -0.003469 0.001047 -0.000126 -0.000142 2.996893e-04 -0.000061 ... 0.4 0.65 0.08 1.0 0.349066 0.2 0.087673 0.318159 20° 0.08
26 1.393050 0.089307 0.048629 -0.002012 -0.003558 0.001354 -0.000096 -0.000240 3.159606e-04 -0.000099 ... 0.4 0.65 0.08 1.0 0.349066 0.2 0.088925 0.305914 20° 0.08
27 1.510394 0.385975 0.034734 -0.010259 -0.000794 -0.000713 -0.000342 0.000435 -6.106588e-07 0.000077 ... 0.4 0.65 0.08 1.0 0.349066 0.2 0.077160 0.372707 20° 0.08
28 1.521174 0.167297 0.058388 -0.004676 -0.002244 0.001328 0.000450 0.000133 1.237559e-04 -0.000085 ... 0.4 0.65 0.08 1.0 0.349066 0.2 0.083355 0.268017 20° 0.08
29 1.364556 0.269302 0.025877 -0.006852 -0.003181 -0.000500 -0.000550 0.000141 2.802554e-04 0.000081 ... 0.4 0.65 0.08 1.0 0.349066 0.2 0.083693 0.406805 20° 0.08
30 1.497570 0.231904 0.047980 -0.006423 -0.002178 0.000534 0.000109 0.000229 1.266806e-04 -0.000008 ... 0.4 0.65 0.08 1.0 0.349066 0.2 0.081946 0.311726 20° 0.08
31 -0.964406 -0.547059 0.002380 -0.009928 0.003770 0.001159 0.001010 0.000236 1.190184e-04 0.000090 ... 0.4 0.65 0.16 1.0 2.443461 0.2 0.173997 2.326146 140° 0.16
32 -0.995482 -0.588329 0.007357 -0.011664 0.004883 0.001041 0.000952 0.000277 3.944896e-04 -0.000080 ... 0.4 0.65 0.16 1.0 2.443461 0.2 0.178168 2.367912 140° 0.16
33 -1.084258 -0.534348 0.027336 -0.012744 0.001073 -0.000244 0.000548 -0.000191 1.501517e-04 0.000003 ... 0.4 0.65 0.16 1.0 2.443461 0.2 0.166143 2.497124 140° 0.16
34 -1.103990 -0.332649 0.037067 -0.008456 -0.004835 -0.001270 0.000250 -0.000419 -4.231776e-04 0.000217 ... 0.4 0.65 0.16 1.0 2.443461 0.2 0.142324 2.521001 140° 0.16
35 -1.081148 -0.481900 0.029774 -0.011638 -0.000656 0.000172 -0.000081 -0.000507 1.641188e-04 -0.000331 ... 0.4 0.65 0.16 1.0 2.443461 0.2 0.159544 2.492420 140° 0.16
36 -0.944329 -0.560544 -0.001406 -0.009806 0.004606 0.001423 0.000978 0.000315 2.221716e-04 -0.000010 ... 0.4 0.65 0.16 1.0 2.443461 0.2 0.176739 2.296786 140° 0.16
37 -0.947443 -0.515159 0.000247 -0.008860 0.002833 0.001258 0.000954 0.000162 -2.426856e-05 0.000138 ... 0.4 0.65 0.16 1.0 2.443461 0.2 0.170455 2.304695 140° 0.16
38 -1.071686 -0.381330 0.029611 -0.008917 -0.003227 -0.000261 0.000127 -0.000506 -2.460090e-04 -0.000004 ... 0.4 0.65 0.16 1.0 2.443461 0.2 0.148710 2.478740 140° 0.16
39 -1.129563 -0.340092 0.042721 -0.009278 -0.005135 -0.001730 0.000113 -0.000464 -3.810645e-04 0.000181 ... 0.4 0.65 0.16 1.0 2.443461 0.2 0.141903 2.555287 140° 0.16
40 -1.160450 -0.608589 0.042258 -0.017069 0.002021 -0.001675 0.000585 -0.000203 4.786634e-04 0.000006 ... 0.4 0.65 0.16 1.0 2.443461 0.2 0.171313 2.610918 140° 0.16
41 -0.100311 -0.512332 -0.061102 0.000277 0.003444 -0.000393 -0.001453 0.000471 6.027916e-05 -0.000128 ... 0.4 0.65 0.16 1.0 1.396263 0.2 0.176521 1.244129 80° 0.16
42 -0.079981 -0.441534 -0.061914 0.000620 0.001185 -0.000248 -0.001414 0.000194 -1.683988e-05 0.000093 ... 0.4 0.65 0.16 1.0 1.396263 0.2 0.167533 1.252522 80° 0.16
43 -0.259471 -0.477805 -0.061752 -0.000556 0.003429 0.000534 -0.000928 0.000441 -6.780287e-06 -0.000085 ... 0.4 0.65 0.16 1.0 1.396263 0.2 0.175097 1.438110 80° 0.16
44 -0.302468 -0.225359 -0.060633 0.001001 -0.002737 0.001199 -0.000374 -0.000215 -2.950729e-04 0.000315 ... 0.4 0.65 0.16 1.0 1.396263 0.2 0.146855 1.570428 80° 0.16
45 -0.152787 -0.194943 -0.065320 0.001167 -0.003807 0.000371 -0.000481 -0.000161 -2.123430e-04 0.000508 ... 0.4 0.65 0.16 1.0 1.396263 0.2 0.143005 1.422087 80° 0.16
46 -0.307667 -0.469121 -0.060844 -0.000789 0.003396 0.000805 -0.000743 0.000429 -3.411932e-05 -0.000063 ... 0.4 0.65 0.16 1.0 1.396263 0.2 0.174511 1.497144 80° 0.16
47 -0.275157 -0.439585 -0.061692 -0.000384 0.002311 0.000701 -0.000792 0.000313 -8.908570e-05 0.000045 ... 0.4 0.65 0.16 1.0 1.396263 0.2 0.170496 1.470243 80° 0.16
48 -0.052624 -0.296443 -0.062555 0.001237 -0.002283 0.000227 -0.001640 -0.000360 -4.048301e-07 0.000094 ... 0.4 0.65 0.16 1.0 1.396263 0.2 0.151212 1.282264 80° 0.16
49 -0.112605 -0.512113 -0.061268 0.000202 0.003539 -0.000323 -0.001436 0.000476 6.406333e-05 -0.000145 ... 0.4 0.65 0.16 1.0 1.396263 0.2 0.176779 1.257857 80° 0.16
50 -0.274825 -0.335715 -0.059410 0.000581 -0.000319 0.001512 -0.001492 -0.000330 -3.504779e-05 -0.000270 ... 0.4 0.65 0.16 1.0 1.396263 0.2 0.157964 1.505475 80° 0.16
51 0.666826 -0.751041 0.016709 0.010780 0.002460 -0.000799 0.000803 0.000095 -1.436529e-04 0.000089 ... 0.4 0.65 0.16 1.0 0.349066 0.2 0.166466 0.346456 20° 0.16
52 0.578040 -0.806088 0.008922 0.010286 0.005021 -0.001514 0.000503 0.000469 -2.606051e-04 -0.000155 ... 0.4 0.65 0.16 1.0 0.349066 0.2 0.177635 0.386719 20° 0.16
53 0.653467 -0.624203 0.006614 0.009087 -0.000103 -0.000554 -0.000854 -0.000533 1.463360e-04 -0.000157 ... 0.4 0.65 0.16 1.0 0.349066 0.2 0.155776 0.441191 20° 0.16
54 0.572528 -0.783451 0.005996 0.009685 0.004365 -0.001549 0.000388 0.000436 -2.251687e-04 -0.000036 ... 0.4 0.65 0.16 1.0 0.349066 0.2 0.175787 0.407493 20° 0.16
55 0.659641 -0.513237 -0.004183 0.006170 -0.002880 -0.000997 -0.000403 -0.000293 2.174160e-04 0.000516 ... 0.4 0.65 0.16 1.0 0.349066 0.2 0.146865 0.504861 20° 0.16
56 0.608625 -0.678892 0.002987 0.008683 0.001219 -0.001112 -0.000207 -0.000031 -6.913243e-06 0.000121 ... 0.4 0.65 0.16 1.0 0.349066 0.2 0.163440 0.445301 20° 0.16
57 0.686187 -0.696469 0.015975 0.010500 0.001120 -0.000493 0.000174 -0.000254 3.747075e-06 -0.000045 ... 0.4 0.65 0.16 1.0 0.349066 0.2 0.160243 0.366476 20° 0.16
58 0.706113 -0.689142 0.018765 0.010884 0.000886 -0.000274 0.000101 -0.000391 3.554720e-05 -0.000137 ... 0.4 0.65 0.16 1.0 0.349066 0.2 0.158335 0.354346 20° 0.16
59 0.863564 -0.675819 0.039638 0.012348 -0.000697 0.000855 0.001824 -0.000443 5.866481e-05 0.000142 ... 0.4 0.65 0.16 1.0 0.349066 0.2 0.148304 0.235357 20° 0.16
60 0.804667 -0.784796 0.042667 0.014671 0.002395 0.000714 0.001847 -0.000351 -1.425091e-04 -0.000275 ... 0.4 0.65 0.16 1.0 0.349066 0.2 0.160054 0.206858 20° 0.16

60 rows × 22 columns

fig, ax = plt.subplots()
explained_variance_ratio_plot(pca, ax=ax, verbose=True)
Explained variance ratio:
['PC1 0.710479191426253', 'PC2 0.28719947416604474', 'PC3 0.00225194144906072', 'PC4 5.183288037026648e-05', 'PC5 1.587220975385232e-05', 'PC6 8.440474770760988e-07', 'PC7 6.213528822494952e-07', 'PC8 1.0970573778645286e-07', 'PC9 6.60747998962157e-08', 'PC10 2.645205525212001e-08', 'PC11 1.0271482863927864e-08', 'PC12 5.522156049005053e-09']
Cumsum of Explained variance ratio:
['PC1 0.710479191426253', 'PC2 0.9976786655922978', 'PC3 0.9999306070413585', 'PC4 0.9999824399217287', 'PC5 0.9999983121314826', 'PC6 0.9999991561789596', 'PC7 0.9999997775318419', 'PC8 0.9999998872375797', 'PC9 0.9999999533123796', 'PC10 0.9999999797644348', 'PC11 0.9999999900359177', 'PC12 0.9999999955580737']
<Axes: >
../../_images/8933e51063931b0ef590e224d318fd316b40dab41d21710233f4059fda5e4632.png
fig, ax = plt.subplots()
sns.scatterplot(
    data=df_pca,
    x="PC1",
    y="PC2",
    hue="bending_angle",
    hue_order=bending_order,
    style="aspect_ratio",
    palette="Set2",
    ax=ax,
)
<Axes: xlabel='PC1', ylabel='PC2'>
../../_images/72a932599f7f2c37c2dd794ea6ccebe12ebf940e747427e3b830957c9e1009a4.png

Morphospace#

def project_3d_to_2d(points, azim=-60, elev=30):
    """Project 3D points onto 2D plane from given viewpoint.
    Uses the same azimuth/elevation as matplotlib.
    """
    az = np.radians(azim)
    el = np.radians(elev)
    R = np.array([
        [-np.sin(az),            np.cos(az),            0         ],
        [-np.sin(el)*np.cos(az), -np.sin(el)*np.sin(az), np.cos(el)],
    ])
    projected = points @ R.T
    return projected[:, 0], projected[:, 1]


def get_pc_scores_for_morphospace(ax, num=5):
    xrange = np.linspace(ax.get_xlim()[0], ax.get_xlim()[1], num)
    yrange = np.linspace(ax.get_ylim()[0], ax.get_ylim()[1], num)
    return xrange, yrange


def plot_recon_morphs_3d(
    pca,
    efa,
    fig,
    ax,
    n_PCs_xy=[1, 2],
    morph_num=3,
    morph_scale=1.0,
    morph_color="lightgray",
    morph_alpha=0.7,
    view=(-60, 30),
):
    """Plot reconstructed shapes in PC space with oblique 2D projection."""
    pc_scores_h, pc_scores_v = get_pc_scores_for_morphospace(ax, morph_num)
    for pc_score_h in pc_scores_h:
        for pc_score_v in pc_scores_v:
            pc_score = np.zeros(pca.n_components_)
            n_PC_h, n_PC_v = n_PCs_xy
            pc_score[n_PC_h - 1] = pc_score_h
            pc_score[n_PC_v - 1] = pc_score_v

            arr_coef = pca.inverse_transform([pc_score])

            ax_width = ax.get_window_extent().width
            fig_width = fig.get_window_extent().width
            fig_height = fig.get_window_extent().height
            morph_size = morph_scale * ax_width / (fig_width * morph_num)
            loc = ax.transData.transform((pc_score_h, pc_score_v))
            axins = fig.add_axes(
                [
                    loc[0] / fig_width - morph_size / 2,
                    loc[1] / fig_height - morph_size / 2,
                    morph_size,
                    morph_size,
                ],
                anchor="C",
            )

            recon = efa.inverse_transform(arr_coef, norm=False)
            x, y = project_3d_to_2d(recon[0], azim=view[0], elev=view[1])

            axins.plot(
                x.astype(float), y.astype(float), color=morph_color, alpha=morph_alpha
            )
            axins.axis("equal")
            axins.axis("off")
morph_num = 5
morph_scale = 0.8
morph_color = "gray"
morph_alpha = 0.8
view = (-120, 60)


fig = plt.figure(figsize=(16, 16), dpi=200)

#########
# PC1 vs PC2
#########
ax = fig.add_subplot(2, 2, 1)
sns.scatterplot(
    data=df_pca,
    x="PC1",
    y="PC2",
    hue="bending_angle",
    hue_order=bending_order,
    style="aspect_ratio",
    palette="Set2",
    ax=ax,
    legend=True,
)

plot_recon_morphs_3d(
    pca,
    efa,
    morph_num=morph_num,
    morph_scale=morph_scale,
    morph_color=morph_color,
    morph_alpha=morph_alpha,
    fig=fig,
    ax=ax,
    view=view,
)

ax.patch.set_alpha(0)
ax.set(xlabel="PC1", ylabel="PC2")

print("PC1-PC2 done")

#########
# PC2 vs PC3
#########
ax = fig.add_subplot(2, 2, 2)
sns.scatterplot(
    data=df_pca,
    x="PC2",
    y="PC3",
    hue="bending_angle",
    hue_order=bending_order,
    style="aspect_ratio",
    palette="Set2",
    ax=ax,
    legend=True,
)

plot_recon_morphs_3d(
    pca,
    efa,
    morph_num=morph_num,
    morph_scale=morph_scale,
    morph_color=morph_color,
    morph_alpha=morph_alpha,
    fig=fig,
    ax=ax,
    n_PCs_xy=[2, 3],
    view=view,
)

ax.patch.set_alpha(0)
ax.set(xlabel="PC2", ylabel="PC3")

print("PC2-PC3 done")

#########
# PC3 vs PC1
#########
ax = fig.add_subplot(2, 2, 3)
sns.scatterplot(
    data=df_pca,
    x="PC3",
    y="PC1",
    hue="bending_angle",
    hue_order=bending_order,
    style="aspect_ratio",
    palette="Set2",
    ax=ax,
    legend=True,
)

plot_recon_morphs_3d(
    pca,
    efa,
    morph_num=morph_num,
    morph_scale=morph_scale,
    morph_color=morph_color,
    morph_alpha=morph_alpha,
    fig=fig,
    ax=ax,
    n_PCs_xy=[3, 1],
    view=view,
)

ax.patch.set_alpha(0)
ax.set(xlabel="PC3", ylabel="PC1")

print("PC3-PC1 done")

#########
# Explained variance
#########
ax = fig.add_subplot(2, 2, 4)
explained_variance_ratio_plot(pca, ax=ax, verbose=True)
PC1-PC2 done
PC2-PC3 done
PC3-PC1 done
Explained variance ratio:
['PC1 0.710479191426253', 'PC2 0.28719947416604474', 'PC3 0.00225194144906072', 'PC4 5.183288037026648e-05', 'PC5 1.587220975385232e-05', 'PC6 8.440474770760988e-07', 'PC7 6.213528822494952e-07', 'PC8 1.0970573778645286e-07', 'PC9 6.60747998962157e-08', 'PC10 2.645205525212001e-08', 'PC11 1.0271482863927864e-08', 'PC12 5.522156049005053e-09']
Cumsum of Explained variance ratio:
['PC1 0.710479191426253', 'PC2 0.9976786655922978', 'PC3 0.9999306070413585', 'PC4 0.9999824399217287', 'PC5 0.9999983121314826', 'PC6 0.9999991561789596', 'PC7 0.9999997775318419', 'PC8 0.9999998872375797', 'PC9 0.9999999533123796', 'PC10 0.9999999797644348', 'PC11 0.9999999900359177', 'PC12 0.9999999955580737']
<Axes: >
../../_images/089fa73c2186aaa2a9b46e66680034d568b783324654b599f4f5847434f20993.png