以鸢尾花数据为例,用C语言实现PCA算法,并求出4个主成分的贡献率。
时间: 2023-12-03 08:44:52 浏览: 137
PCA(Principal Component Analysis)算法是一种常用的数据降维方法,可以将高维数据降到低维空间中,同时尽可能的保留原始数据的信息。在这里,我们将使用C语言实现PCA算法,并求出4个主成分的贡献率。
鸢尾花数据集是一个经典的数据集,包含了3种不同类别的鸢尾花,每类50个样本。每个样本包括4个特征:花萼长度、花萼宽度、花瓣长度和花瓣宽度。我们将使用这个数据集来进行PCA算法的实现。
首先,我们需要读取数据集并将其存储在一个二维数组中。假设我们将数据集存储在一个名为“iris.csv”的文件中,我们可以使用以下代码来读取数据:
```c
#include <stdio.h>
#include <stdlib.h>
#define ROWS 150
#define COLS 4
int main() {
FILE *fp;
double data[ROWS][COLS];
int i, j;
fp = fopen("iris.csv", "r");
if (fp == NULL) {
printf("Error opening file\n");
return 1;
}
for (i = 0; i < ROWS; i++) {
for (j = 0; j < COLS; j++) {
fscanf(fp, "%lf,", &data[i][j]);
}
}
fclose(fp);
// TODO: 实现PCA算法
// ...
return 0;
}
```
接下来,我们需要对数据进行中心化处理,即将每个特征的平均值减去整个特征向量的平均值。这可以通过以下代码实现:
```c
double mean[COLS] = {0};
for (i = 0; i < COLS; i++) {
for (j = 0; j < ROWS; j++) {
mean[i] += data[j][i];
}
mean[i] /= ROWS;
}
for (i = 0; i < ROWS; i++) {
for (j = 0; j < COLS; j++) {
data[i][j] -= mean[j];
}
}
```
然后,我们需要计算协方差矩阵。协方差矩阵是一个对称矩阵,其中每个元素表示对应特征之间的相关性。我们可以使用以下代码来计算协方差矩阵:
```c
double cov[COLS][COLS] = {0};
for (i = 0; i < COLS; i++) {
for (j = i; j < COLS; j++) {
double sum = 0;
int k;
for (k = 0; k < ROWS; k++) {
sum += data[k][i] * data[k][j];
}
cov[i][j] = cov[j][i] = sum / (ROWS - 1);
}
}
```
接下来,我们需要对协方差矩阵进行特征分解。特征分解可以将协方差矩阵分解成特征向量和特征值的乘积。特征向量是一个列向量,表示对应特征的方向,而特征值表示在该方向上的方差。我们可以使用以下代码来计算特征向量和特征值:
```c
double eig_vals[COLS] = {0};
double eig_vecs[COLS][COLS] = {0};
jacobi(cov, COLS, eig_vals, eig_vecs); // 使用Jacobi方法进行特征分解
```
其中,`jacobi`函数是使用Jacobi方法进行特征分解的函数,可以使用现成的库函数或者自己实现。
最后,我们需要选择前4个特征向量作为主成分,并计算它们的贡献率。主成分是按照特征值从大到小排序的前几个特征向量,它们可以最大限度地保留原始数据的信息。贡献率表示每个主成分对总方差的贡献程度,可以通过对应特征值与所有特征值之和的比值来计算。我们可以使用以下代码来选择主成分并计算贡献率:
```c
int num_pc = 4;
double pc[COLS][num_pc];
for (i = 0; i < num_pc; i++) {
for (j = 0; j < COLS; j++) {
pc[j][i] = eig_vecs[j][COLS - 1 - i];
}
}
double total_var = 0;
for (i = 0; i < COLS; i++) {
total_var += eig_vals[i];
}
double pc_var[num_pc];
for (i = 0; i < num_pc; i++) {
pc_var[i] = eig_vals[COLS - 1 - i] / total_var;
printf("PC%d: %.2f%%\n", i + 1, pc_var[i] * 100);
}
```
其中,`num_pc`表示要选择的主成分的数量。在这个例子中,我们选择了4个主成分。`pc`数组存储了选择的主成分,每列代表一个主成分。`total_var`表示所有特征的方差之和。`pc_var`数组存储了每个主成分的贡献率。最后,使用`printf`函数输出主成分的贡献率。
完整代码如下:
```c
#include <stdio.h>
#include <stdlib.h>
#define ROWS 150
#define COLS 4
void jacobi(double A[][COLS], int n, double eigenvalues[], double eigenvectors[][COLS]);
int main() {
FILE *fp;
double data[ROWS][COLS];
int i, j;
fp = fopen("iris.csv", "r");
if (fp == NULL) {
printf("Error opening file\n");
return 1;
}
for (i = 0; i < ROWS; i++) {
for (j = 0; j < COLS; j++) {
fscanf(fp, "%lf,", &data[i][j]);
}
}
fclose(fp);
double mean[COLS] = {0};
for (i = 0; i < COLS; i++) {
for (j = 0; j < ROWS; j++) {
mean[i] += data[j][i];
}
mean[i] /= ROWS;
}
for (i = 0; i < ROWS; i++) {
for (j = 0; j < COLS; j++) {
data[i][j] -= mean[j];
}
}
double cov[COLS][COLS] = {0};
for (i = 0; i < COLS; i++) {
for (j = i; j < COLS; j++) {
double sum = 0;
int k;
for (k = 0; k < ROWS; k++) {
sum += data[k][i] * data[k][j];
}
cov[i][j] = cov[j][i] = sum / (ROWS - 1);
}
}
double eig_vals[COLS] = {0};
double eig_vecs[COLS][COLS] = {0};
jacobi(cov, COLS, eig_vals, eig_vecs);
int num_pc = 4;
double pc[COLS][num_pc];
for (i = 0; i < num_pc; i++) {
for (j = 0; j < COLS; j++) {
pc[j][i] = eig_vecs[j][COLS - 1 - i];
}
}
double total_var = 0;
for (i = 0; i < COLS; i++) {
total_var += eig_vals[i];
}
double pc_var[num_pc];
for (i = 0; i < num_pc; i++) {
pc_var[i] = eig_vals[COLS - 1 - i] / total_var;
printf("PC%d: %.2f%%\n", i + 1, pc_var[i] * 100);
}
return 0;
}
void jacobi(double A[][COLS], int n, double eigenvalues[], double eigenvectors[][COLS]) {
int i, j, k;
for (i = 0; i < n; i++) {
eigenvectors[i][i] = 1;
for (j = 0; j < n; j++) {
if (i != j) {
eigenvectors[i][j] = 0;
}
}
}
int max_iter = n * n * n;
for (i = 0; i < max_iter; i++) {
double max_offdiag = 0;
int p, q;
for (j = 0; j < n; j++) {
for (k = j + 1; k < n; k++) {
double a = A[j][k];
if (abs(a) > max_offdiag) {
max_offdiag = abs(a);
p = j;
q = k;
}
}
}
if (max_offdiag == 0) {
break;
}
double theta = (A[q][q] - A[p][p]) / (2 * A[p][q]);
double t;
if (theta >= 0) {
t = 1 / (theta + sqrt(1 + theta * theta));
} else {
t = -1 / (-theta + sqrt(1 + theta * theta));
}
double c = 1 / sqrt(1 + t * t);
double s = t * c;
double tau = s / (1 + c);
double a_pp = A[p][p];
double a_qq = A[q][q];
double a_pq = A[p][q];
A[p][p] = a_pp - t * a_pq;
A[q][q] = a_qq + t * a_pq;
A[p][q] = A[q][p] = 0;
for (j = 0; j < n; j++) {
if (j != p && j != q) {
double a_pj = A[p][j];
double a_qj = A[q][j];
A[p][j] = A[j][p] = a_pj - s * (a_qj + tau * a_pj);
A[q][j] = A[j][q] = a_qj + s * (a_pj - tau * a_qj);
}
}
for (j = 0; j < n; j++) {
double v_pj = eigenvectors[p][j];
double v_qj = eigenvectors[q][j];
eigenvectors[p][j] = v_pj - s * (v_qj + tau * v_pj);
eigenvectors[q][j] = v_qj + s * (v_pj - tau * v_qj);
}
}
for (i = 0; i < n; i++) {
eigenvalues[i] = A[i][i];
}
}
```
注意:这里的PCA算法实现中,我们使用了Jacobi方法进行特征分解。这种方法虽然简单易懂,但其时间复杂度较高,通常不适用于大规模的数据集。在实际应用中,可能需要使用更快速的算法,例如基于奇异值分解(SVD)的PCA算法。
阅读全文