2016-01-28 2 views
8

У меня есть прямоугольник (нельзя считать квадратным) Pandas DataFrame чисел. Скажем, я выбираю диагональное направление (либо «верхний правый, либо нижний» или «сверху вниз»). Я хотел бы вычислить серию, чьи записи представляют собой суммы значений из исходного DataFrame вдоль выбранного набора параллельных диагоналей. Чтобы полностью определить цель, вам нужно решить, являются ли диагонали «закреплены» слева или «привязаны» справа. Для ниже, я предполагаю, что они «привязаны» слева.Прямой способ генерации суммы всех параллельных диагоналей в Numpy/Pandas?

я могу сделать это без особых проблем:

import numpy as np 
import pandas as pd 

rectdf = pd.DataFrame(np.arange(15).reshape(5,3)) 

# result: 
    0 1 2 
0 0 1 2 
1 3 4 5 
2 6 7 8 
3 9 10 11 
4 12 13 14 

я могу вычислить «левого верхнего к lowerright» диагональные суммы следующим образом:

ullrsums = pd.concat([rectdf.iloc[:, i].shift(-i) for i in range(rectdf.shape[1])], axis=1)\ 
    .sum(axis=1, fillna=0) 

# result: 
0 12 
1 21 
2 30 
3 22 
4 12 

И я могу вычислить «upperright к lowerleft "диагональные суммы по листать shift(-i) к shift(i) в предыдущем:

urllsums = pd.concat([rectdf.iloc[:, i].shift(i) for i in range(rectdf.shape[1])], axis=1)\ 
    .sum(axis=1, fillna=0) 

# result: 
0  0 
1  4 
2 12 
3 21 
4 30 

Эти результаты являются правильными (т. этот код делает то, что я хочу). Есть ли более прямой способ вычислить эти суммы в Pandas или Numpy?

+0

См. Также http://stackoverflow.com/q/10792897 и http://stackoverflow.com/q/28917414 –

ответ

6

Вы можете искать numpy.trace(), документально here, чтобы получить след непосредственно, или numpy.diagonal() получить диагональный вектор, documented here

Во-первых, конвертировать dataframe в Numpy матрице с использованием rectdf.as_matrix()

Тогда:

np.trace(matrix, offset) 

Смещение, которое может быть положительным или отрицательным, требует смещения.

Например, если мы делаем:

a = np.arange(15).reshape(5, 3) 
for x in range(-4, 3): print np.trace(a, x) 

Мы получаем вывод:

12 
22 
30 
21 
12 
6 
2 

Чтобы сделать это для общей матрицы, мы хотим, чтобы диапазон от -(rows - 1) до columns, то есть, если у нас есть переменная rows и переменная columns:

a = np.arange(rows * columns).reshape(rows, columns) 
for x in range(-(rows - 1), columns): print np.trace(a, x) 
0

Короткий ответ

См. Быструю, но сложную функцию в конце.

развитие

Итерация по trace хорошо, но я не уверен, что это лучше, чем решение панд. Оба включают итерацию - над диагоналями или столбцами. Концептуально проще или чище, но я не уверен в скорости, особенно на больших массивах.

Каждая диагональ имеет разную длину, [[12],[9,13],...]. Это большой красный флаг, предупреждающий нас о том, что операция блока блоков сложна, если не невозможна.

С scipy.sparse я могу построить 2d массив, который может быть суммированный дать эти следы:

In [295]: from scipy import sparse 
In [296]: xs=sparse.dia_matrix(x) 
In [297]: xs.data 
Out[297]: 
array([[12, 0, 0], 
     [ 9, 13, 0], 
     [ 6, 10, 14], 
     [ 3, 7, 11], 
     [ 0, 4, 8], 
     [ 0, 1, 5], 
     [ 0, 0, 2]]) 
In [298]: np.sum(xs.data,axis=1) 
Out[298]: array([12, 22, 30, 21, 12, 6, 2]) 

Этот редкий формат хранит data в 2d массив, с необходимыми сдвигами. На самом деле ваш pd.concat производит что-то подобное:

In [304]: pd.concat([rectdf.iloc[:, i].shift(-i) for i in range(rectdf.shape[1])], axis=1) 
Out[304]: 
    0 1 2 
0 0 4 8 
1 3 7 11 
2 6 10 14 
3 9 13 NaN 
4 12 NaN NaN 

Похоже sparse создает этот data массив, начиная с np.zeros, и заполняя его с соответствующей индексацией:

data[row_indices, col_indices] = x.ravel() 

что-то вроде:

In [344]: i=[4,5,6,3,4,5,2,3,4,1,2,3,0,1,2] 
In [345]: j=[0,1,2,0,1,2,0,1,2,0,1,2,0,1,2] 
In [346]: z=np.zeros((7,3),int) 
In [347]: z[i,j]=x.ravel()[:len(i)] 
In [348]: z 
Out[348]: 
array([[12, 0, 0], 
     [ 9, 13, 0], 
     [ 6, 10, 14], 
     [ 3, 7, 11], 
     [ 0, 4, 8], 
     [ 0, 1, 5], 
     [ 0, 0, 2]]) 

хотя мне все еще нужен способ создания i,j для любой формы. Для j легко:

j=np.tile(np.arange(3),5) 
j=np.tile(np.arange(x.shape[1]),x.shape[0]) 

Перестройка i

In [363]: np.array(i).reshape(-1,3) 
Out[363]: 
array([[4, 5, 6], 
     [3, 4, 5], 
     [2, 3, 4], 
     [1, 2, 3], 
     [0, 1, 2]]) 

приводит меня к воссозданию его:

In [371]: ii=(np.arange(3)+np.arange(5)[::-1,None]).ravel() 
In [372]: ii 
Out[372]: array([4, 5, 6, 3, 4, 5, 2, 3, 4, 1, 2, 3, 0, 1, 2]) 

Так вместе:

def all_traces(x): 
    jj = np.tile(np.arange(x.shape[1]),x.shape[0]) 
    ii = (np.arange(x.shape[1])+np.arange(x.shape[0])[::-1,None]).ravel() 
    z = np.zeros(((x.shape[0]+x.shape[1]-1),x.shape[1]),int) 
    z[ii,jj] = x.ravel() 
    return z.sum(axis=1) 

Он нуждается в большем количестве тестирование на различных формах.

Эта функция работает быстрее, чем итерации по следам, даже с этой небольшой массив размера:

In [387]: timeit all_traces(x) 
10000 loops, best of 3: 70.5 µs per loop 
In [388]: timeit [np.trace(x,i) for i in range(-(x.shape[0]-1),x.shape[1])] 
10000 loops, best of 3: 106 µs per loop 
0

Для 2D-Numpy массива A это может быть самый короткий код подводить диагоналей (?):

np.bincount(sum(np.indices(A.shape)).flat, A.flat) 

Чтобы суммировать противоположные диагонали, вы можете np.fliplr массив.

Смежные вопросы