Numpy sum несколько столбцов

Anom спросил: 12 мая 2018 в 04:00 в: python

Я хочу читать файлы со следующей структурой:

1         3      7        34
1         4      3         6
1         8      2        10
2         9     35        11
2        10     12        24
...

количество столбцов меняется из одного файла в другой, и я хочу суммировать все значения столбцов для 1, 2 и т. Д.

Итак, результаты должны быть чем-то вроде

1       15     12      50
2       19     37      45
...

Я начал кодировать что-то вроде:

import numpy as np
import sysinFile = sys.argv[1]A = np.genfromtxt(inFile, delimiter=None)a = np.size(A,1) #get the number of columnsi = np.nonzero(np.diff(A[:, 0]))[0] + 1
i = np.insert(i, 0, 0)c1 = A[i, 0]
for l in range(3,a+1):
    c = np.add.reduceat(A[:, l], i)
    result = np.c_[c1, c]

Как это сделать с помощью numpy?

2 ответа

Есть решение
sacul ответил: 12 мая 2018 в 04:52

Метод Loopy numpy

Это не самый красивый способ, и, скорее всего, это векторный подход, который намного более изящный и эффективный, но вы можете сделать следующее со списком. См. Нижнюю часть для эталона.

Для двухмерного массива x:

>>> x
array([[ 1,  3,  7, 34],
       [ 1,  4,  3,  6],
       [ 1,  8,  2, 10],
       [ 2,  9, 35, 11],
       [ 2, 10, 12, 24]])np.vstack([np.insert(x[x[:,0] == i][:,1:].sum(axis=0),0,i)
           for i in np.unique(x[:,0])])

Возвраты:

array([[ 1, 15, 12, 50],
       [ 2, 19, 47, 35]])

pandas

Для этого типа табличных данных вы можете рассмотреть pandas , Например:

import pandas as pddf = pd.DataFrame(x)>>> df
   0   1   2   3
0  1   3   7  34
1  1   4   3   6
2  1   8   2  10
3  2   9  35  11
4  2  10  12  24>>> df.groupby(0).sum()    1   2   3
0            
1  15  12  50
2  19  47  35

Вы также можете легко вернуть его в массив numpy:

>>> df.groupby(0).sum().reset_index().values
array([[ 1, 15, 12, 50],
       [ 2, 19, 47, 35]])

Benchmarks

К моему удивлению, оба метода сопоставимы по скорости, мой метод loopy numpy узко избивает pandas метод: в большом массиве формы (100000, 100) метод loopy numpy занял в среднем около 0,1 секунды, а метод pandas занял в среднем 0,13 секунды

import timeitx = np.random.randint(0,10,(100000, 100))def np_method(arr = x):
    return np.vstack([np.insert(arr[arr[:,0] == i][:,1:].sum(axis=0),0,i)
                      for i in np.unique(arr[:,0])])def pd_method(df = pd.DataFrame(x)):
    return df.groupby(0).sum().reset_index().values>>> timeit.timeit(pd_method, number = 100) / 100
0.12673938989639283>>> timeit.timeit(np_method, number = 100) / 100
0.09724574089050293
Anom ответил: 13 мая 2018 в 07:14
Большое спасибо, я пробовал метод numpy и отлично работает
Yakym Pirozhenko ответил: 12 мая 2018 в 05:52

Вот полностью векторизованный метод, предполагая, что идентификаторы сортируются. Идея:

  1. Вычислить cumum всего набора данных.
  2. Возьмите последнюю строку для каждого идентификатора.
  3. Сделайте разницу между последовательными идентификаторами.
  4. Reindex. литий> ол>
    def tally(xs):
        # index of the last id in each group
        id_ix = np.searchsorted(xs[:, 0], np.unique(xs[:, 0]), "right") - 1
        # compute cumulative sum over all ids;
        # drop every line that is not final for some id
        csums = xs.cumsum(0)[id_ix]
        # take differences between adjacent groups
        csums[1:] = np.diff(csums, axis=0)
        # recover old IDs
        csums[:, 0] = xs[id_ix, 0]
        return csums# ys.shape == (1_000_000, 10)
    # In [377]: %timeit q.np_method(q.ys)
    # 5.74 s ± 47.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)# In [378]: %timeit q.tally(q.ys)
    # 143 ms ± 1.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
Yakym Pirozhenko ответил: 12 мая 2018 в 06:26
Weird. Я использовал float, а не ints в своем тесте. Версия для печати 1.14.3.
sacul ответил: 12 мая 2018 в 06:17
Мне нравится этот метод, но, по-видимому, я получил значительно разные результаты по сравнению с эталонами: x.shape = (1000000, 10), tally(x) занял в среднем 0,23 секунды, а np_method(x) в среднем 0,07 секунды. numpy версия: '1.13.3'
Yakym Pirozhenko ответил: 12 мая 2018 в 06:31
@sacul Я думаю, что понял. Я использовал 1000 идентификаторы в своем тесте. Если я использую 10, как в вашем тесте, ваш метод выполняется быстрее.
sacul ответил: 12 мая 2018 в 06:33
О, интересно ... Ну, я думаю, это зависит от требований OP, но в любом случае у вас есть мой upvote :)
Yakym Pirozhenko ответил: 12 мая 2018 в 06:34
Спасибо, тоже!