Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning groups of correlated columns in pandas data frame

I've run a correlation matrix on a pandas DataFrame:

df=pd.DataFrame( {'one':[0.1, .32, .2, 0.4, 0.8], 'two':[.23, .18, .56, .61, .12], 'three':[.9, .3, .6, .5, .3], 'four':[.34, .75, .91, .19, .21], 'zive': [0.1, .32, .2, 0.4, 0.8], 'six':[.9, .3, .6, .5, .3], 'drive':[.9, .3, .6, .5, .3]})

corrMatrix=df.corr()
corrMatrix
           drive  four   one   six  three   two  zive
drive       1.00 -0.04 -0.75  1.00   1.00  0.24 -0.75
four       -0.04  1.00 -0.49 -0.04  -0.04  0.16 -0.49
one        -0.75 -0.49  1.00 -0.75  -0.75 -0.35  1.00
six         1.00 -0.04 -0.75  1.00   1.00  0.24 -0.75
three       1.00 -0.04 -0.75  1.00   1.00  0.24 -0.75
two         0.24  0.16 -0.35  0.24   0.24  1.00 -0.35
zive       -0.75 -0.49  1.00 -0.75  -0.75 -0.35  1.00

Now, I want to write some code to return the columns that are perfectly correlated (ie correlation ==1) in groups.

Optimally, I would want this: [['zive', 'one'], ['three', 'six', 'drive']]

I've written the below code, which gives me ['drive', 'one', 'six', 'three', 'zive'], but as you can see, they are just a bag of columns that have some sort of perfect correlation with some other column-- it does not put them in a distinctive grouping with their perfectly correlated cousin columns.

correlatedCols=[]
for col in corrMatrix:
    data=corrMatrix[col][corrMatrix[col]==1]
    if len(data)>1:
        correlatedCols.append(data.name)

correlatedCols  
['drive','one', 'six', 'three', 'zive']

EDIT: Using the advice given by @Karl D., I get this:

cor = df.corr()
cor.loc[:,:] =  np.tril(cor.values, k=-1)
cor = cor.stack()
cor[cor ==1]
six    drive   1.00
three  drive   1.00
       six     1.00
zive   one     1.00

..which is not quite what I want -- since [six, drive] is not a grouping -- it's missing 'three'.

like image 920
Bryan Avatar asked Jun 02 '14 20:06

Bryan


2 Answers

Here is a naive approach:

df=pd.DataFrame( {'one':[0.1, .32, .2, 0.4, 0.8], 'two':[.23, .18, .56, .61, .12], 'three':[.9, .3, .6, .5, .3], 'four':[.34, .75, .91, .19, .21], 'zive': [0.1, .32, .2, 0.4, 0.8], 'six':[.9, .3, .6, .5, .3], 'drive':[.9, .3, .6, .5, .3]})

corrMatrix=df.corr()

corrMatrix.loc[:,:] =  np.tril(corrMatrix, k=-1) # borrowed from Karl D's answer

already_in = set()
result = []
for col in corrMatrix:
    perfect_corr = corrMatrix[col][corrMatrix[col] == 1].index.tolist()
    if perfect_corr and col not in already_in:
        already_in.update(set(perfect_corr))
        perfect_corr.append(col)
        result.append(perfect_corr)

Result:

>>> result
[['six', 'three', 'drive'], ['zive', 'one']]
like image 77
Akavall Avatar answered Oct 08 '22 15:10

Akavall


You could do something like the following:

>>> cor = df.corr()
>>> cor.loc[:,:] =  np.tril(cor, k=-1)
>>> cor = cor.stack()
>>> cor[cor > 0.9999]

three  six    1
zive   one    1

To match more closely your expected output you can do something like the following:

>>> cor[cor > 0.9999].to_dict().keys()

[('zive', 'one'), ('three', 'six')]

Explanation. First, I create a lower triangular version of the covariance matrix that excludes the diagonal (using numpy's tril):

>>> cor.loc[:,:] =  np.tril(cor.values, k=-1)

           four       one       six     three       two  zive
four   0.000000 -0.000000 -0.000000 -0.000000  0.000000    -0
one   -0.489177  0.000000 -0.000000 -0.000000 -0.000000     0
six   -0.039607 -0.747365  0.000000  0.000000  0.000000    -0
three -0.039607 -0.747365  1.000000  0.000000  0.000000    -0
two    0.159583 -0.351531  0.238102  0.238102  0.000000    -0
zive  -0.489177  1.000000 -0.747365 -0.747365 -0.351531     0

And then I stack the dataframe:

>>> cor = cor.stack()

four   four     0.000000
       one     -0.000000
       six     -0.000000
       three   -0.000000
       two      0.000000
       zive    -0.000000
one    four    -0.489177
       one      0.000000
       six     -0.000000
       three   -0.000000
       two     -0.000000
       zive     0.000000
six    four    -0.039607
       one     -0.747365
       six      0.000000
       three    0.000000
       two      0.000000
       zive    -0.000000
three  four    -0.039607
       one     -0.747365
       six      1.000000
       three    0.000000
       two      0.000000
       zive    -0.000000
two    four     0.159583
       one     -0.351531
       six      0.238102
       three    0.238102
       two      0.000000
       zive    -0.000000
zive   four    -0.489177
       one      1.000000
       six     -0.747365
       three   -0.747365
       two     -0.351531
       zive     0.000000

And then I can just grab the rows that equal one.

Edit: I think this will get the form you want but it's not elegant:

>>> from itertools import chain

>>> cor.loc[:,:] =  np.tril(cor, k=-1)
>>> cor = cor.stack()
>>> ones = cor[cor > 0.999].reset_index().loc[:,['level_0','level_1']]
>>> ones = ones.query('level_0 not in level_1')
>>> ones.groupby('level_0').agg(lambda x: set(chain(x.level_0,x.level_1))).values

[[set(['six', 'drive', 'three'])]
 [set(['zive', 'one'])]]
like image 27
Karl D. Avatar answered Oct 08 '22 13:10

Karl D.