Задайте значения на диагонали pandas.DataFrame

У меня есть фреймворк pandas, я хотел бы поставить диагональ на 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

теперь я хочу установить диагональ в 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

но должен быть более пуфонический путь!?

Ответы

Ответ 1

In [21]: df.values[[np.arange(5)]*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

Обратите внимание, что это будет работать, только если df имеет такое же количество строк, что и столбцы. Другой способ, который будет работать для произвольных форм, - использовать np.fill_diagonal:

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

Ответ 2

Оба подхода в unutbu answer предполагают, что метки неактуальны (они работают с базовыми значениями).

Код OP работает с .loc, и вместо этого используется метка (т.е. помещает 0 в ячейки в столбце строки с одинаковыми метками, а не в ячейки, расположенные по диагонали - по общему признанию, это не имеет значения в конкретном примере данный, в котором метки являются только позициями).

Будучи нуждающимся в диагональном заполнении "на основе ярлыков" (работая с DataFrame, описывающим неполную матрицу смежности), самый простой подход, который я мог придумать, заключался в следующем:

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

Это решение векторизовано и очень быстро и, если другое предлагаемое решение не работает для любых имен столбцов и размера матрицы 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)

Производительность в Dataframe из 507 столбцов и строк

% timeit pd_fill_diagonal(df, 0)

1000 циклов, лучше всего 3: 145 мкс за цикл

Ответ 4

Вот хак, который работал у меня:

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)