因子分析について、Pythonでの実装は非常に簡単にできます。
ただ、理論については、何となくわかった気になっては、テキストを読み返す、ということを繰り返していたので、Pythonのコードも交えた学習メモを作成してみました。
Pythonに関心のない方は、コード部分を読み飛ばしていただければと思います。ライブラリを使えば、本記事で記載したコードを書かなくても因子分析はできますが、学習のために書いています。
因子分析とは
因子分析は統計的な手法で、多くの変数から共通の因子を抽出することを目指します。それは、一連の観測値に対して潜在的な構造を特定するために使用されます。例えば、心理学でパーソナリティ特性を測定するためや、マーケティングで消費者の行動を理解するために使われます。
因子分析の手順
- 相関行列の作成:まずはじめに、それぞれの変数間の相関行列を作成します。相関行列は、変数間の相関係数を含む行列です。
- 固有値と固有ベクトルの計算:次に、相関行列の固有値と固有ベクトルを計算します。固有値は、それぞれの因子がデータ全体に対してどれだけの説明力を持つかを示します。一方、固有ベクトルはそれぞれの因子が元の変数にどの程度影響を与えるかを示します。
- 因子の選択:次に、適切な数の因子を選択します。通常は、固有値が1以上の因子を選びます。これは「カイザーの基準」と呼ばれます。その他にも「スクリープロット」や「累積寄与率」などを用いて因子の数を選ぶこともあります。
- 因子負荷の計算:選択された各因子に対して、それぞれの変数がどの程度貢献しているかを示す因子負荷を計算します。
- 因子の回転:最後に、解釈を容易にするために、因子を回転させます。これにより、各変数が特定の因子にどれだけ貢献しているかをより明確にします。
相関行列の固有値と固有ベクトル
固有値と固有ベクトルは、ある行列を特徴付ける重要な数学的な特性です。これらは、元の行列が表現するデータの内部構造を特定するのに非常に便利で、因子分析や主成分分析のような次元削減の手法で広く使用されます。
相関行列(あるいは共分散行列)の固有値と固有ベクトルを計算すると、次のような関係があります:
- 固有値(Eigenvalues):固有値は、相関行列の各主軸(あるいは方向)に沿ってデータがどの程度散らばっているかを示します。より大きな固有値を持つ主軸ほど、データはその軸に沿ってより散らばっています。そのため、それぞれの固有値が、データ全体に対してどれだけの「説明力」を持つかを示しています。
- 固有ベクトル(Eigenvectors):一方、固有ベクトルは、それぞれの主軸(あるいは方向)がデータ空間でどのように向かっているかを示します。具体的には、それぞれの固有ベクトルの成分(すなわち、ベクトルの各要素)は、元の変数(ここではテストのスコア)がその主軸にどの程度寄与するかを示します。つまり、それぞれの固有ベクトルが、元の変数がその主軸(あるいは因子)にどの程度影響を与えるかを示しています。
主軸の数と背景
正方行列(例えば、n×n行列)の固有方程式からは、最大でn個の固有値が得られる。これらの固有値にはそれぞれ対応する固有ベクトルが存在し、データの主軸を表す。
・n×n 行列の固有方程式はn次の方程式になり、n次の方程式は最大でn個の解を持つことができる。
・相関行列や共分散行列は実対称行列であり、固有ベクトルは互いに直交することが知られており、得られる固有ベクトルは独立している。
固有値と固有ベクトルの概念は、線形代数における行列の固有値問題から派生しています。行列の「固有値」と「固有ベクトル」は、行列を特徴付ける数学的な属性であり、行列を対角化するときに特に重要な役割を果たします。
具体的には、ある行列Aに対して、次の等式を満たすスカラーλ(これが固有値)とベクトルv(これが固有ベクトル)を見つけることが目的となります。
Av = λv
相関行列(または共分散行列)の場合、固有ベクトルは、その行列が表すデータセットの分散が最大となる方向を示します。そして、その方向に沿った分散の大きさが、その固有ベクトルに対応する固有値となります。したがって、固有値が大きければ大きいほど、データはその軸(すなわち、その固有ベクトルが示す方向)に沿ってより広く散らばっていると言えます。
共分散行列の固有ベクトルと分散の関係
共分散行列Cとベクトルvの積Cvを考えてみる。このベクトルCvは、元のベクトルvがデータのどの方向に沿っているかに基づいて、その方向の変動(分散)を強調または減少させる効果がある。具体的には、データの分散が最も強い方向に沿ったベクトルは、共分散行列によって最も「強く伸ばされる」ことになる。
固有ベクトルは、共分散行列Cによってスケーリングされた後、その方向が変わらない特殊なベクトルである。言い換えると、固有ベクトルvは、共分散行列Cによって単にスケーリング(拡大/縮小)されるだけで、その方向は変わらない。
このスケーリング因子が固有値λ。
数式で表すと Cv=λv。
この式から、固有ベクトルvが共分散行列Cによってどの程度強くスケーリングされるかが固有値λで決まることがわかる。
最も大きな固有値に対応する固有ベクトルは、データの分散が最大となる方向を示す。なぜなら、それは共分散行列によって最も強くスケーリングされる方向だから。次に大きな固有値に対応する固有ベクトルは、残った方向の中で最も分散が大きな方向を示し、というように続く。
Python(相関行列の作成と固有値計算)
例を用いてPythonコードを記載してみます。4つのテストスコア (Test1, Test2, Test3, Test4) から共通の因子を見つけるという仮定で行います。
まず、以下のようなデータセットがあるとします。
| Student | Test1 | Test2 | Test3 | Test4 |
|---------|-------|-------|-------|-------|
| A | 85 | 90 | 92 | 88 |
| B | 75 | 78 | 80 | 76 |
| C | 95 | 90 | 88 | 94 |
| D | 90 | 92 | 94 | 90 |
| E | 80 | 78 | 82 | 84 |
これらのデータを使用して、まず相関行列を計算します。Pythonのpandasとnumpyのライブラリを使用した場合、以下のようになります。
import pandas as pd
import numpy as np
# データセットの作成
data = {'Test1': [85, 75, 95, 90, 80],
'Test2': [90, 78, 90, 92, 78],
'Test3': [92, 80, 88, 94, 82],
'Test4': [88, 76, 94, 90, 84]}
df = pd.DataFrame(data)
# 相関行列の計算
correlation_matrix = df.corr()
print(correlation_matrix)
次に、相関行列の固有値と固有ベクトルを計算します。これは、numpyのlinalg.eig関数を使って行うことができます。
eigenvalues, eigenvectors = np.linalg.eig(correlation_matrix)
print("Eigenvalues:")
print(eigenvalues)
print("Eigenvectors:")
print(eigenvectors)
上記のコードは相関行列の固有値と固有ベクトルを出力します。固有値はそれぞれの因子が全体の分散に対してどれだけの説明力を持つかを示しています。固有ベクトルはそれぞれの因子が元の変数にどの程度影響を与えるかを示しています。
以上のようにして、相関行列の作成と固有値の計算を行います。
因子負荷の計算
因子分析の主要な目的は、相関行列(または共分散行列)を説明するためのより少ない数の因子を見つけることであるため、因子負荷量の計算は、因子分析の中心的な部分となります。
- 固有ベクトルの取得: 固有ベクトルは、相関行列または共分散行列から計算されたものです。各固有ベクトルは、それぞれの因子を示しており、その要素は各観測変数に対応しています。
- 因子負荷量の解釈: 固有ベクトルの各要素(または成分)は、それぞれの観測変数と新しい因子との関係を示しています。この関係を「因子負荷量」と呼びます。具体的には、特定の固有ベクトルにおける変数Aの要素は、その因子における変数Aの因子負荷量を示しています。
- 因子の回転(オプション): 固有ベクトルをそのまま因子負荷量として解釈することもできますが、実際の因子分析では、因子負荷量の解釈をより明確にするために、固有ベクトルに回転を施すことが一般的です。最も一般的な回転手法にはVarimax回転やQuartimax回転などがあります。回転後のベクトルの要素もまた、因子負荷量として解釈されます。
Python(因子負荷量の計算)
前出のPythonコードに因子負荷量の計算を追加します。このコードでは、固有ベクトル自体が因子負荷量として解釈されています。
import pandas as pd
import numpy as np
# データセットの作成
data = {'Test1': [85, 75, 95, 90, 80],
'Test2': [90, 78, 90, 92, 78],
'Test3': [92, 80, 88, 94, 82],
'Test4': [88, 76, 94, 90, 84]}
df = pd.DataFrame(data)
# 相関行列の計算
correlation_matrix = df.corr()
eigenvalues, eigenvectors = np.linalg.eig(correlation_matrix)
# 因子負荷量の計算
factor_loadings = eigenvectors
# それぞれの因子(固有ベクトル)に対する因子負荷量を表示
num_factors = factor_loadings.shape[1]
for factor_num in range(num_factors):
print(f"\nFactor {factor_num + 1} loadings:")
for test_num, test_name in enumerate(df.columns):
print(f"{test_name}: {factor_loadings[test_num, factor_num]:.4f}")
因子負荷行列
因子負荷行列は、因子分析における中心的なコンセプトです。この行列は、元の観測変数と抽出された因子との関係を示すものです。
具体的には、因子負荷行列は以下の点を示しています:
- 行は元の観測変数に対応しています。
- 列は抽出された各因子に対応しています。
この行列の各エントリ(要素)は「因子負荷量」として知られ、観測変数と因子との関係の強さを示します。
因子負荷量の解釈:
- 正の値: その観測変数がその因子と正の関係があることを示す。
- 負の値: その観測変数がその因子と負の関係があることを示す。
- ゼロに近い値: その観測変数とその因子との間に強い関係はないことを示す。
因子負荷行列の例は以下のとおりです。
Factor1 Factor2
Test1 0.75 -0.20
Test2 0.80 0.15
Test3 0.72 -0.10
Test4 0.65 0.25
上記の例では、Test1はFactor1と強く正の関係があることが示されていますが、Factor2の関係は比較的弱いです。
因子分析の目的の1つは、多数の観測変数をより少数の因子で効果的に表現することです。因子負荷行列は、この変換がどのように行われるのかを明示的に示してくれます。
因子の回転
因子の回転とは、因子分析において因子負荷量を解釈しやすくするための手法です。元の因子(固有ベクトルに基づく)は、必ずしも観測変数との関係が明確でない場合があります。回転によって、各因子が観測変数の一部のみに影響を与えるようにすることが目的です。
回転の種類
- オルソゴナル回転(直交回転): この回転は、因子間の直交性を保持します。つまり、回転後の因子同士が互いに直角の関係を保つようにします。Varimax回転がこのタイプのもっとも一般的な手法です。
- オブリーク回転: この回転では、因子間の直交性を保持せず、因子間の相関を許容します。Promax回転やOblimin回転がこのタイプに該当します。
回転の目的
- 解釈のしやすさ: 因子負荷量が0または1に近い値を持つ変数が増えると、どの変数がどの因子に対応するのかの解釈が容易になります。
- 明確な変数のグルーピング: 回転により、一部の変数が特定の因子に強くローディングされ、他の因子にはローディングされないようになることが多いです。
例
考え方をシンプルにするため、2つの因子と2つの変数を考えます。これを2D平面上にプロットすると、因子は2つの直交する軸として表示されます。回転前は、各変数はこれらの軸に対してある角度で位置しますが、回転を適用すると、変数が1つの軸に近づき、もう1つの軸から遠ざかるようになります。
このようなグラフィカルなイメージは、3つ以上の変数や因子を持つ複雑なケースにも適用できますが、次元が増えると可視化は困難になります。
要するに、回転は、因子と変数の関係を明確にし、解釈を容易にするための工夫です。
Python(因子の回転)
以下は、Varimax回転を使用して、先に計算した因子負荷量を回転させるコードの例です。
import pandas as pd
import numpy as np
from factor_analyzer import Rotator
# データセットの作成
data = {'Test1': [85, 75, 95, 90, 80],
'Test2': [90, 78, 90, 92, 78],
'Test3': [92, 80, 88, 94, 82],
'Test4': [88, 76, 94, 90, 84]}
df = pd.DataFrame(data)
# 相関行列の計算
correlation_matrix = df.corr()
eigenvalues, eigenvectors = np.linalg.eig(correlation_matrix)
# 因子負荷量の計算
factor_loadings = eigenvectors
# それぞれの因子(固有ベクトル)に対する因子負荷量を表示
num_factors = factor_loadings.shape[1]
for factor_num in range(num_factors):
print(f"\nFactor {factor_num + 1} loadings:")
for test_num, test_name in enumerate(df.columns):
print(f"{test_name}: {factor_loadings[test_num, factor_num]:.4f}")
# Varimax回転の実施
rotator = Rotator(method='varimax')
rotated_factor_loadings = rotator.fit_transform(factor_loadings)
# 回転後の因子負荷量を表示
print("\nRotated Factor Loadings (Varimax):")
for factor_num in range(num_factors):
print(f"\nFactor {factor_num + 1} loadings after rotation:")
for test_num, test_name in enumerate(df.columns):
print(f"{test_name}: {rotated_factor_loadings[test_num, factor_num]:.4f}")
おわりに
ライブラリを使えば簡単に統計解析ができますが、背景理論について、あれどうだったけ?となることがあり、まとめてみました。他の分析についても書いてみようかと思います。
関連記事
因子負荷行列の解釈をChatGPTに実施させる試みを記事にしました。ご興味ありましたらご覧ください。