主成分分析で次元を落とす
世の中には2種類のデータがあります。Nが大きいデータとpが大きいデータです。
Nはサンプル数、pは特徴量の数を指します。例えばコロナウイルスのデータでは、1つのデータに対して「患者年齢」「患者状態」などの特徴量が10個程度なのに対し、サンプル数は数百、数千と存在するため、Nが大きいデータです。
一方、老化細胞データでは、1つの細胞データに対して数万種類の遺伝子におけるmRNA数が用意されています。そして、実験によっては十分な細胞数を確保できないことがあるため、そのときはサンプル数Nに対して遺伝子数pが多くなり、pが大きいデータとなります。
一般に、pが大きいデータはNが大きいデータに比べて解析が難しい傾向があります。そこで、pを減らすための方法が色々と開発されています。本記事では、特徴量の数pを減らすための「次元削減法」の一つである「主成分分析」について紹介し、実際に使ってみます。
次元とは何か
高校数学でよく見るデカルト座標は、2次元または3次元のものがあります。
データ分析においては、特徴量の数pを「次元数」と呼ぶことがあります。高校数学の座標のように、p次元のデカルト座標として表示することができます。
この図は3次元の例ですが、4次元以上のデータに関しても同じように考えることができます。
サンプル数と次元数の関係
データ分析において、次元数に比べてサンプル数が小さいと、色々と困ることがあります。
例えば、2次元座標において、データの回帰直線を書いてみます。
このように、サンプル数が多ければ、問題なく回帰直線を書くことができます。
それでは、サンプル数を減らすとどうなるでしょうか。
サンプル数が次元数を下回ると、一意な回帰直線を引くことができなくなります。同じことが、3次元のグラフでも言えます。
3次元では平面の回帰式が作成されますが、サンプル数が2以下だと平面の向きが一定に定まらず、一意の平面が特定できません。
他にも、機械学習ではデータの次元数が大きいと学習すべきパラメータが増えるため、学習負荷が爆発的に増える「次元の呪い」が発動します。
このように、サンプル数に対して次元数が大きいと様々な問題が発生してしまいます。
主成分分析の仕組み
そこで、主成分分析を使うことで次元数を減らすことができます。
例えば、上図の2次元グラフを1次元に落とすことを考えてみます。主成分分析では、「新たな軸」を作成することで、2次元のデータを別の1次元のデータとして表すことができます。
これまで、(x1, x2)の座標で表されていたサンプル点が、(Z)の数直線で表すことができます。
ところで、軸のとり方は複数考えられますが、どの向きで軸を引くのがよいでしょうか。主成分分析では、新たな軸上のサンプルの「分散」が最大となるような軸を作成します。
上図の(Z)と(Z')では、軸の向きを意図的に変えています。(Z')の方では、ほとんどのサンプルが(Z')上でほぼ同じ所にプロットされています。これでは、サンプル間の違いが分かりづらいため、後の解析で使いづらくなります。
一方で、(Z)の方はサンプルの分散が大きいため、サンプル間の違いが分かりやすいです。主成分分析ではこのように分散が大きくなるよう次元削減を行ってくれるため、分析前のデータ前処理として適しています。
ちなみに、軸は複数引くことができます。ただし、それぞれの軸は他の軸と非相関でなければならないため、2本の軸は必ず直交になります。したがって、2次元のグラフでは2本まで、3次元のグラフなら3本まで軸を作成することができます。
次元削減の場合、例えば50次元のデータを5本の軸で表すといったことがなされます。たとえ5本しか軸がなかったとしても、分散が大きくデータをよく分割することができていれば、データの特徴を十分に表すことができます。
次元削減の効果
最後に、主成分分析で次元削減を行ったことで、後のデータ分析でどのように影響するかを示して終わりたいと思います。
今回の例では、老化細胞データを主成分分析で50次元まで削減した後に、t-SNEとUMAPで可視化を行ってみます。
# 準備 import os import pandas as pd import scanpy as sc from matplotlib import pyplot as plt plt.style.use('ggplot')
# データ取得 os.system('wget https://storage.googleapis.com/calico-website-mca-storage/lung.h5ad') adata = sc.read_h5ad(fname) df = pd.DataFrame(adata.X.todense()).T.astype(int) df.index = adata.var['gene_ids-0'].tolist()
# 主成分分析 from sklearn.decomposition import PCA pca = PCA(n_components=50) pca.fit(df) feature = pca.transform(df) pca_df = pd.DataFrame(data=feature, index=df.index)
主成分分析を行うことで、データサイズが(10299, 20755)だったのが、(10299, 50)と50次元まで削減することができました。
このデータを使ってt-SNEとUMAPを実行します。
# t-SNE実行 from sklearn.manifold import TSNE d = TSNE(n_components=2).fit_transform(pca_df) plt.scatter(d[:,0], d[:,1], s=10)
# UMAP実行 import umap mapper = umap.UMAP(random_state=0) e = mapper.fit_transform(pca_df) plt.scatter(e[:,0], e[:,1], s=10)
t-SNE所要時間 | UMAP所要時間 | |
---|---|---|
PCAあり | 2分1秒 | 0分48秒 |
PCAなし | 60分以上(終わらず) | 測定不能(メモリエラー) |
次元数を50まで減らしたことで、明らかに計算負荷が削減されたことが実感できます。主成分分析(PCA)を行わないと、20755次元のデータをそのままt-SNEやUMAPに適用することになるため、計算がいつまで経っても終わらなかったり、メモリ不足で計算が中断されてしまったりします。
このことから、次元数pが大きいデータは主成分分析を行って次元削減した方が良さそうです。
まとめ
この記事では、データの次元を減らすことの意義と、主成分分析で次元削減を行う手順について説明しました。
老化細胞データのような遺伝子解析データでは、サンプル数に対して次元数(遺伝子数)が多くなりがちなので、今回のような次元削減方法は多くの研究で採用されています。
主成分分析はかなり有名な方法なので、Python以外にも様々な分析ツールで実行可能です。また、色々と派生方法も多く面白い次元削減方法でもあります。ぜひ、自分に合った主成分分析を見つけて、自身の研究解析に活かしてみてください。
以上です。