Principal Component Analysis and Regression in Python

I'm trying to figure out how to reproduce in Python some work that I've done in SAS. Using this dataset, where multicollinearity is a problem, I would like to perform principal component analysis in Python. I've looked at scikit-learn and statsmodels, but I'm uncertain how to take their output and convert it to the same results structure as SAS. For one thing, SAS appears to perform PCA on the correlation matrix when you use PROC PRINCOMP, but most (all?) of the Python libraries appear to use SVD.

In the dataset, the first column is the response variable and the next 5 are predictive variables, called pred1-pred5.

In SAS, the general workflow is:

/* Get the PCs */
proc princomp data=indata out=pcdata;
var pred1 pred2 pred3 pred4 pred5;
run;

/* Standardize the response variable */
proc standard data=pcdata mean=0 std=1 out=pcdata2;
var response;
run;

/* Compare some models */
proc reg data=pcdata2;
Reg:     model response = pred1 pred2 pred3 pred4 pred5 / vif;
PCa:     model response = prin1-prin5 / vif;
PCfinal: model response = prin1 prin2 / vif;
run;
quit;

/* Use Proc PLS to to PCR Replacement - dropping pred5 */
/* This gets me my parameter estimates for the original data */
proc pls data=indata method=pcr nfac=2;
model response = pred1 pred2 pred3 pred4 / solution;
run;
quit;


I know that the last step only works because I'm only choosing PC1 and PC2, in order.

So, in Python, this is about as far as I've gotten:

import pandas as pd
import numpy  as np
from sklearn.decomposition.pca import PCA

# Create a pandas DataFrame object
frame = pd.DataFrame(source)

# Make sure we are working with the proper data -- drop the response variable
cols = [col for col in frame.columns if col not in ['response']]
frame2 = frame[cols]

pca = PCA(n_components=5)
pca.fit(frame2)


The amount of variance that each PC explains?

print pca.explained_variance_ratio_

Out[190]:
array([  9.99997603e-01,   2.01265023e-06,   2.70712663e-07,
1.11512302e-07,   2.40310191e-09])


What are these? Eigenvectors?

print pca.components_

Out[179]:
array([[ -4.32840645e-04,  -7.18123771e-04,  -9.99989955e-01,
-4.40303223e-03,  -2.46115129e-05],
[  1.00991662e-01,   8.75383248e-02,  -4.46418880e-03,
9.89353169e-01,   5.74291257e-02],
[ -1.04223303e-02,   9.96159390e-01,  -3.28435046e-04,
-8.68305757e-02,  -4.26467920e-03],
[ -7.04377522e-03,   7.60168675e-04,  -2.30933755e-04,
5.85966587e-02,  -9.98256573e-01],
[ -9.94807648e-01,  -1.55477793e-03,  -1.30274879e-05,
1.00934650e-01,   1.29430210e-02]])


Are these the eigenvalues?

print pca.explained_variance_

Out[180]:
array([  8.07640319e+09,   1.62550137e+04,   2.18638986e+03,
9.00620474e+02,   1.94084664e+01])


I'm at a bit of a loss on how to get from the Python results to actually performing Principal Component Regression (in Python). Do any of the Python libraries fill in the blanks to similarly to SAS?

Any tips are appreciated. I'm a little spoiled by the use of labels in SAS output and I'm not very familiar with pandas, numpy, scipy, or scikit-learn.

Edit:

So, it looks like sklearn won't operate directly on a pandas dataframe. Let's say that I convert it to a numpy array:

npa = frame2.values
npa


Here's what I get:

Out[52]:
array([[  8.45300000e+01,   4.20730000e+02,   1.99443000e+05,
7.94000000e+02,   1.21100000e+02],
[  2.12500000e+01,   2.73810000e+02,   4.31180000e+04,
1.69000000e+02,   6.28500000e+01],
[  3.38200000e+01,   3.73870000e+02,   7.07290000e+04,
2.79000000e+02,   3.53600000e+01],
...,
[  4.71400000e+01,   3.55890000e+02,   1.02597000e+05,
4.07000000e+02,   3.25200000e+01],
[  1.40100000e+01,   3.04970000e+02,   2.56270000e+04,
9.90000000e+01,   7.32200000e+01],
[  3.85300000e+01,   3.73230000e+02,   8.02200000e+04,
3.17000000e+02,   4.32300000e+01]])


If I then change the copy parameter of sklearn's PCA to False, it operates directly on the array, per the comment below.

pca = PCA(n_components=5,copy=False)
pca.fit(npa)

npa


Per the output, it looks like it replaced all of the values in npa instead of appending anything to the array. What are the values in npa now? The principal component scores for the original array?

Out[64]:
array([[  3.91846649e+01,   5.32456568e+01,   1.03614689e+05,
4.06726542e+02,   6.59830027e+01],
[ -2.40953351e+01,  -9.36743432e+01,  -5.27103110e+04,
-2.18273458e+02,   7.73300268e+00],
[ -1.15253351e+01,   6.38565684e+00,  -2.50993110e+04,
-1.08273458e+02,  -1.97569973e+01],
...,
[  1.79466488e+00,  -1.15943432e+01,   6.76868901e+03,
1.97265416e+01,  -2.25969973e+01],
[ -3.13353351e+01,  -6.25143432e+01,  -7.02013110e+04,
-2.88273458e+02,   1.81030027e+01],
[ -6.81533512e+00,   5.74565684e+00,  -1.56083110e+04,
-7.02734584e+01,  -1.18869973e+01]])

• In scikit-learn, each sample is stored as a row in your data matrix. The PCA class operate on the data matrix directly i.e., it takes care of computing the covariance matrix, and then its eigenvectors. Regarding your final 3 questions, yes, components_ are the eigenvectors of the covariance matrix, explained_variance_ratio_ are the variance each PC explains, and the explained variance should correspond to the eigenvalues. – lightalchemist Jan 13 '14 at 7:45
• @lightalchemist Thank you for the clarification. With sklearn, is it proper to create a new dataframe prior to performing the PCA, or is it possible to send in the 'complete' pandas dataframe and have it not operate on the leftmost (response) column? – Clay Jan 13 '14 at 11:33
• I added a little more info. If I convert to an numpy array first and then run PCA with copy=False, I get new values. Are those the principal component scores? – Clay Jan 13 '14 at 11:55
• I'm not that familiar with Pandas so I don't have a reply to that part of your question. Regarding the second part, I don't think they are the principal component. I believe they are the original data samples but with the mean subtracted. However, I cannot really be sure about it. – lightalchemist Jan 13 '14 at 12:07

Scikit-learn does not have a combined implementation of PCA and regression like for example the pls package in R. But I think one can do like below or choose PLS regression.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from sklearn.preprocessing import scale
from sklearn.decomposition import PCA
from sklearn import cross_validation
from sklearn.linear_model import LinearRegression

%matplotlib inline

import seaborn as sns
sns.set_style('darkgrid')

X = df.iloc[:,1:6]
y = df.response


Scikit-learn PCA

pca = PCA()


Scale and transform data to get Principal Components

X_reduced = pca.fit_transform(scale(X))


Variance (% cumulative) explained by the principal components

np.cumsum(np.round(pca.explained_variance_ratio_, decimals=4)*100)

array([  73.39,   93.1 ,   98.63,   99.89,  100.  ])


Seems like the first two components indeed explain most of the variance in the data.

10-fold CV, with shuffle

n = len(X_reduced)
kf_10 = cross_validation.KFold(n, n_folds=10, shuffle=True, random_state=2)

regr = LinearRegression()
mse = []


Do one CV to get MSE for just the intercept (no principal components in regression)

score = -1*cross_validation.cross_val_score(regr, np.ones((n,1)), y.ravel(), cv=kf_10, scoring='mean_squared_error').mean()
mse.append(score)


Do CV for the 5 principle components, adding one component to the regression at the time

for i in np.arange(1,6):
score = -1*cross_validation.cross_val_score(regr, X_reduced[:,:i], y.ravel(), cv=kf_10, scoring='mean_squared_error').mean()
mse.append(score)

fig, (ax1, ax2) = plt.subplots(1,2, figsize=(12,5))
ax1.plot(mse, '-v')
ax2.plot([1,2,3,4,5], mse[1:6], '-v')
ax2.set_title('Intercept excluded from plot')

for ax in fig.axes:
ax.set_xlabel('Number of principal components in regression')
ax.set_ylabel('MSE')
ax.set_xlim((-0.2,5.2))


Scikit-learn PLS regression

mse = []

kf_10 = cross_validation.KFold(n, n_folds=10, shuffle=True, random_state=2)

for i in np.arange(1, 6):
pls = PLSRegression(n_components=i, scale=False)
pls.fit(scale(X_reduced),y)
score = cross_validation.cross_val_score(pls, X_reduced, y, cv=kf_10, scoring='mean_squared_error').mean()
mse.append(-score)

plt.plot(np.arange(1, 6), np.array(mse), '-v')
plt.xlabel('Number of principal components in PLS regression')
plt.ylabel('MSE')
plt.xlim((-0.2, 5.2))


Here's SVD in Python and NumPy only (years later).
(This doesn't address your questions about SSA / sklearn / pandas at all, but may help a pythonist someday.)

#!/usr/bin/env python2
""" SVD straight up """
# geometry: see http://www.ams.org/samplings/feature-column/fcarc-svd

from __future__ import division
import sys
import numpy as np

__version__ = "2015-06-15 jun  denis-bz-py t-online de"

# from bz.etc import numpyutil as nu
def ints( x ):
return np.round(x).astype(int)  # NaN Inf -> - maxint

def quantiles( x ):
return "quantiles %s" % ints( np.percentile( x, [0, 25, 50, 75, 100] ))

#...........................................................................
csvin = "ccheaton-multicollinearity.csv"  # https://gist.github.com/ccheaton/8393329
plot = 0

# to change these vars in sh or ipython, run this.py  csvin=\"...\"  plot=1  ...
for arg in sys.argv[1:]:
exec( arg )

np.set_printoptions( threshold=10, edgeitems=10, linewidth=120,
formatter = dict( float = lambda x: "%.2g" % x ))  # float arrays %.2g

#...........................................................................
yX = np.loadtxt( csvin, delimiter="," )
y = yX[:,0]
X = yX[:,1:]
print "y %d  %s" % (len(y), quantiles(y))
print "X %s  %s" % (X.shape, quantiles(X))
print ""

#...........................................................................
U, sing, Vt = np.linalg.svd( X, full_matrices=False )
#...........................................................................

print "SVD: %s -> U %s . sing diagonal . Vt %s" % (
X.shape, U.shape, Vt.shape )
print "singular values:", ints( sing )
# % variance (sigma^2) explained != % sigma explained, e.g. 10 1 1 1 1

var = sing**2
var *= 100 / var.sum()
print "% variance ~ sing^2:", var

print "Vt, the right singular vectors  * 100:\n", ints( Vt * 100 )
# multicollinear: near +- 100 in each row / col

yU = y.dot( U )
yU *= 100 / yU.sum()
print "y ~ these percentages of U, the left singular vectors:", yU


--> log

# from: test-pca.py
# run: 15 Jun 2015 16:45  in ~bz/py/etc/data/etc  Denis-iMac 10.8.3
# versions: numpy 1.9.2  scipy 0.15.1   python 2.7.6   mac 10.8.3

y 373  quantiles [  2823  60336  96392 147324 928560]
X (373, 5)  quantiles [     7     47    247    573 512055]

SVD: (373, 5) -> U (373, 5) . sing diagonal . Vt (5, 5)
singular values: [2537297    4132    2462     592      87]
% variance ~ sing^2: [1e+02 0.00027 9.4e-05 5.4e-06 1.2e-07]
Vt, the right singular vectors  * 100:
[[  0   0 100   0   0]
[  1  98   0 -12  17]
[-10 -11   0 -99  -6]
[  1 -17   0  -4  98]
[-99   2   0  10   2]]
y ~ these percentages of U, the left singular vectors: [1e+02 15 -18 0.88 -0.57]

• I'm a bit late to the party but great answer – plumbus_bouquet May 14 '16 at 5:49

Try using a pipeline to combine principle components analysis and linear regression:

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline

# Principle components regression
steps = [
('scale', StandardScaler()),
('pca', PCA()),
('estimator', LinearRegression())
]
pipe = Pipeline(steps)
pca = pipe.set_params(pca__n_components=3)
pca.fit(X, y)


My answer is coming almost five years late and there is a good chance that you don't need help regarding doing PCR in Python any longer. We have developed a Python package named hoggorm that does exactly what you needed back then. Please have a look at the PCR examples here. There is also a complementary plotting package named hoggormplot for visualisation of results computed with hoggorm.