Pandas columnas correlación con significación estadística.

¿Cuál es la mejor manera, dada una estructura de datos de pandas, df, para obtener la correlación entre sus columnas df.1 y df.2 ?

No quiero que la salida cuente las filas con NaN , lo que hace la correlación incorporada de los pandas . Pero también quiero que pvalue un valor de pvalue o un error estándar, lo que no hace el incorporado.

SciPy parece quedar atrapado por los NaN, aunque creo que sí reporta significación.

Ejemplo de datos:

  1 2 0 2 NaN 1 NaN 1 2 1 2 3 -4 3 4 1.3 1 5 NaN NaN 

Respuesta proporcionada por @Shashank es agradable. Sin embargo, si quieres una solución en pandas puros, te puede gustar esto:

 import pandas as pd from pandas.io.data import DataReader from datetime import datetime import scipy.stats as stats gdp = pd.DataFrame(DataReader("GDP", "fred", start=datetime(1990, 1, 1))) vix = pd.DataFrame(DataReader("VIXCLS", "fred", start=datetime(1990, 1, 1))) #Do it with a pandas regression to get the p value from the F-test df = gdp.merge(vix,left_index=True, right_index=True, how='left') vix_on_gdp = pd.ols(y=df['VIXCLS'], x=df['GDP'], intercept=True) print(df['VIXCLS'].corr(df['GDP']), vix_on_gdp.f_stat['p-value']) 

Resultados:

 -0.0422917932738 0.851762475093 

Los mismos resultados que la función de estadísticas:

 #Do it with stats functions. df_clean = df.dropna() stats.pearsonr(df_clean['VIXCLS'], df_clean['GDP']) 

Resultados:

  (-0.042291793273791969, 0.85176247509284908) 

Para extenderme a más vairables te doy un enfoque basado en un bucle feo:

 #Add a third field oil = pd.DataFrame(DataReader("DCOILWTICO", "fred", start=datetime(1990, 1, 1))) df = df.merge(oil,left_index=True, right_index=True, how='left') #construct two arrays, one of the correlation and the other of the p-vals rho = df.corr() pval = np.zeros([df.shape[1],df.shape[1]]) for i in range(df.shape[1]): # rows are the number of rows in the matrix. for j in range(df.shape[1]): JonI = pd.ols(y=df.icol(i), x=df.icol(j), intercept=True) pval[i,j] = JonI.f_stat['p-value'] 

Resultados de rho:

  GDP VIXCLS DCOILWTICO GDP 1.000000 -0.042292 0.870251 VIXCLS -0.042292 1.000000 -0.004612 DCOILWTICO 0.870251 -0.004612 1.000000 

Resultados de pval:

  [[ 0.00000000e+00 8.51762475e-01 1.11022302e-16] [ 8.51762475e-01 0.00000000e+00 9.83747425e-01] [ 1.11022302e-16 9.83747425e-01 0.00000000e+00]] 

Puede usar las funciones de correlación scipy.stats para obtener el valor p.

Por ejemplo, si está buscando una correlación como la correlación de Pearson, puede usar la función pearsonr .

 from scipy.stats import pearsonr pearsonr([1, 2, 3], [4, 3, 7]) 

Da salida

 (0.7205766921228921, 0.48775429164459994) 

Donde el primer valor en la tupla es el valor de correlación, y el segundo es el valor p.

En su caso, puede usar la función dropna los pandas para eliminar primero los valores de NaN .

 df_clean = df[['column1', 'column2']].dropna() pearsonr(df_clean['column1'], df_clean['column2']) 

Para calcular todos los valores de p a la vez , puede usar la siguiente función de valores de calculate_pvalues :

 df = pd.DataFrame({'A':[1,2,3], 'B':[2,5,3], 'C':[5,2,1], 'D':['text',2,3] }) calculate_pvalues(df) 
  • La salida es similar a la corr() (pero con valores p):

      ABC A 0 0.7877 0.1789 B 0.7877 0 0.6088 C 0.1789 0.6088 0 
  • Los valores de p se redondean a 4 decimales.

  • La columna D se ignora ya que contiene texto.

A continuación se muestra el código de la función :

 from scipy.stats import pearsonr import pandas as pd def calculate_pvalues(df): df = df.dropna()._get_numeric_data() dfcols = pd.DataFrame(columns=df.columns) pvalues = dfcols.transpose().join(dfcols, how='outer') for r in df.columns: for c in df.columns: pvalues[r][c] = round(pearsonr(df[r], df[c])[1], 4) return pvalues 
 rho = df.corr() rho = rho.round(2) pval = calculate_pvalues(df) # toto_tico's answer # create three masks r1 = rho.applymap(lambda x: '{}*'.format(x)) r2 = rho.applymap(lambda x: '{}**'.format(x)) r3 = rho.applymap(lambda x: '{}***'.format(x)) # apply them where appropriate rho = rho.mask(pval<=0.1,r1) rho = rho.mask(pval<=0.05,r2) rho = rho.mask(pval<=0.01,r3) rho # note I prefer readability over the conciseness of code, # instead of six lines it could have been a single liner like this: # [rho.mask(pval<=p,rho.applymap(lambda x: '{}*'.format(x)),inplace=True) for p in [.1,.05,.01]] 

Correlaciones con asteriscos

He intentado sumr la lógica en una función, puede que no sea el enfoque más eficiente, pero le proporcionará un resultado similar al de pandas df.corr (). Para usar esto, simplemente ponga la siguiente función en su código y llámelo proporcionando su objeto de dataframe, es decir. corr_pvalue (your_dataframe) .

He redondeado los valores a 4 decimales, en caso de que desee una salida diferente, cambie el valor en la función de redondeo.

 from scipy.stats import pearsonr import numpy as np import pandas as pd def corr_pvalue(df): numeric_df = df.dropna()._get_numeric_data() cols = numeric_df.columns mat = numeric_df.values arr = np.zeros((len(cols),len(cols)), dtype=object) for xi, x in enumerate(mat.T): for yi, y in enumerate(mat.T[xi:]): arr[xi, yi+xi] = map(lambda _: round(_,4), pearsonr(x,y)) arr[yi+xi, xi] = arr[xi, yi+xi] return pd.DataFrame(arr, index=cols, columns=cols) 

Lo he probado con pandas v0.18.1

Ese código fue muy útil por oztalha . Acabo de cambiar el formato (redondeado a 2 dígitos) donde r no fue significativo.

  rho = data.corr() pval = calculate_pvalues(data) # toto_tico's answer # create three masks r1 = rho.applymap(lambda x: '{:.2f}*'.format(x)) r2 = rho.applymap(lambda x: '{:.2f}**'.format(x)) r3 = rho.applymap(lambda x: '{:.2f}***'.format(x)) r4 = rho.applymap(lambda x: '{:.2f}'.format(x)) # apply them where appropriate --this could be a single liner rho = rho.mask(pval>0.1,r4) rho = rho.mask(pval<=0.1,r1) rho = rho.mask(pval<=0.05,r2) rho = rho.mask(pval<=0.01,r3) rho 

Grandes respuestas de @toto_tico y @ Somendra-joshi. Sin embargo, baja valores de NA innecesarios. En este fragmento, solo estoy eliminando las NA que pertenecen a la correlación que se está calculando en este momento. En la implementación real corr , hacen lo mismo.

 def calculate_pvalues(df): df = df._get_numeric_data() dfcols = pd.DataFrame(columns=df.columns) pvalues = dfcols.transpose().join(dfcols, how='outer') for r in df.columns: for c in df.columns: if c == r: df_corr = df[[r]].dropna() else: df_corr = df[[r,c]].dropna() pvalues[r][c] = pearsonr(df_corr[r], df_corr[c])[1] return pvalues 

En pandas v0.24.0 se agregó un argumento de method a corr . Ahora, puedes hacer:

 import pandas as pd import numpy as np from scipy.stats import pearsonr df = pd.DataFrame({'A':[1,2,3], 'B':[2,5,3], 'C':[5,2,1]}) df.corr(method=lambda x, y: pearsonr(x, y)[1]) - np.eye(len(df.columns)) 
  ABC A 0.000000 0.787704 0.178912 B 0.787704 0.000000 0.608792 C 0.178912 0.608792 0.000000 

Tenga en cuenta la solución que se necesita con np.eye(len(df.columns)) , porque las auto correlaciones siempre se establecen en 1.0 (consulte https://github.com/pandas-dev/pandas/issues/25726 ).