web-dev-qa-db-fra.com

Définir des valeurs sur la diagonale de pandas.DataFrame

J'ai un cadre de données de pandas Je voudrais voir la diagonale à 0

import numpy
import pandas

df = pandas.DataFrame(numpy.random.Rand(5,5))
df

Out[6]:
     0           1           2           3               4
0    0.536596    0.674319    0.032815    0.908086    0.215334
1    0.735022    0.954506    0.889162    0.711610    0.415118
2    0.119985    0.979056    0.901891    0.687829    0.947549
3    0.186921    0.899178    0.296294    0.521104    0.638924
4    0.354053    0.060022    0.275224    0.635054    0.075738
5 rows × 5 columns

maintenant je veux mettre la diagonale à 0:

for i in range(len(df.index)):
    for j in range(len(df.columns)):
        if i==j:
            df.loc[i,j] = 0
df
Out[9]:
     0           1           2           3           4
0    0.000000    0.674319    0.032815    0.908086    0.215334
1    0.735022    0.000000    0.889162    0.711610    0.415118
2    0.119985    0.979056    0.000000    0.687829    0.947549
3    0.186921    0.899178    0.296294    0.000000    0.638924
4    0.354053    0.060022    0.275224    0.635054    0.000000
5 rows × 5 columns

mais il doit y avoir un moyen plus pythonique que ça!?

20
Tim
In [21]: df.values[[np.arange(df.shape[0])]*2] = 0

In [22]: df
Out[22]: 
          0         1         2         3         4
0  0.000000  0.931374  0.604412  0.863842  0.280339
1  0.531528  0.000000  0.641094  0.204686  0.997020
2  0.137725  0.037867  0.000000  0.983432  0.458053
3  0.594542  0.943542  0.826738  0.000000  0.753240
4  0.357736  0.689262  0.014773  0.446046  0.000000

Notez que cela ne fonctionnera que si df a le même nombre de lignes que de colonnes. Une autre façon de travailler avec des formes arbitraires consiste à utiliser np.fill_diagonal :

In [36]: np.fill_diagonal(df.values, 0)
38
unutbu

Les deux approches de la réponse de unutbu / supposent que les étiquettes ne sont pas pertinentes (elles agissent sur les valeurs sous-jacentes).

Le code OP fonctionne avec .loc et est donc basé sur les étiquettes (c.-à-d. Mettez un 0 sur les cellules rangée-colonne avec les mêmes étiquettes, plutôt que sur les cellules situées sur la diagonale - certes, cela n’a pas ne sont que des positions).

Ayant besoin d'un remplissage en diagonale "basé sur une étiquette" (en utilisant une variable DataFrame décrivant une matrice de adjacence incomplète), l'approche la plus simple que j'ai pu trouver était la suivante:

def pd_fill_diagonal(df, value):
    idces = df.index.intersection(df.columns)
    stacked = df.stack(dropna=False)
    stacked.update(pd.Series(value,
                             index=pd.MultiIndex.from_arrays([idces,
                                                              idces])))
    df.loc[:, :] = stacked.unstack()
3

Cette solution est vectorisée et très rapide et à moins que l’autre solution suggérée ne fonctionne pour n’importe quel nom de colonne et quelle que soit la taille de la matrice df.

def pd_fill_diagonal(df_matrix, value=0): 
    mat = df_matrix.values
    n = mat.shape[0]
    mat[range(n), range(n)] = value
    return pd.DataFrame(mat)

Performance sur Dataframe de 507 colonnes et lignes

% timeit pd_fill_diagonal(df, 0)

1000 boucles, le meilleur des 3: 145 µs par boucle

1
Philipp Schwarz

Utiliser np.fill_diagonal(df.values, 1) est la solution la plus simple, mais vous devez vous assurer que vos colonnes ont toutes le même type de données. J'avais un mélange de np.float64 et de python floats et cela affecterait uniquement les valeurs numpy. pour réparer vous devez tout jeter à numpy.

0
Andrew Louw

Voici un hack qui a fonctionné pour moi:

def set_diag(self, values): 
    n = min(len(self.index), len(self.columns))
    self.values[[np.arange(n)] * 2] = values
pd.DataFrame.set_diag = set_diag

x = pd.DataFrame(np.random.randn(10, 5))
x.set_diag(0)
0
qed