Pandas: эффективная векторизация плотной таблицы в разреженную форму

#python #pandas #numpy #dataframe

#python #pandas #numpy #фрейм данных

Вопрос:

Я пытаюсь преобразовать плотную таблицу в ее разреженную форму. Код неэффективен, и мне было интересно, есть ли более эффективный способ сделать это в pandas / numpy.

Требуется импорт:

 >>> import pandas as pd
>>> import numpy as np
>>> import functools
>>> import collections
  

Входные данные:

 >>> df_dict = [ {'A': 'a', 'N':1, 'value':11}, {'A': 'b', 'N':4, 'value':12} , {'A': 'c', 'N':3, 'value':13} , {'A': 'd', 'N':2, 'value':14} , {'A': 'a', 'N':3, 'value':15} , {'A': 'b', 'N':5, 'value':16}  , {'A': 'c', 'N':1, 'value':17} ]   
>>> df = pd.DataFrame(df_dict)
>>> df
   A  N  value
0  a  1     11
1  b  4     12
2  c  3     13
3  d  2     14
4  a  3     15
5  b  5     16
6  c  1     17
  

A и N представляют 2 оси, для которых мы собираемся построить новую разреженную таблицу AxN. Эта новая таблица будет иметь ключ ‘A’ со значениями, представляющими массив numpy, так что эта таблица при индексации по A и N возвращает либо значение, если оно существует, либо NaN в противном случае.

Ось:

 >>> all_As = ['a', 'b', 'c', 'd', 'e']
>>> all_Ns = [0,1,2,3,4,5]
  

Примечание: значения в N являются произвольными упорядочиваемыми объектами, не обязательно индексируемыми в какой-либо массив.

Лучшее, что у меня есть до сих пор:

 >>> dct = collections.defaultdict(lambda: collections.defaultdict(lambda: float("nan")))
>>> for _, row in df.iterrows():
...   dct[row['A']][row['N']] = row['value']
>> output_sparse_table = collections.defaultdict(functools.partial(np.zeros, shape=(len(all_Ns)), dtype="float32"))
>>> for a in all_As:
...   for n in all_Ns:
...     output_sparse_table[a][n] = dct[a][n]
  

Для этого требуется 2 прохода: один раз по фрейму данных для построения запрашиваемого dct и второй по каждому элементу в матрице AxN. Есть ли способ сделать это более эффективно (возможно, с помощью векторизации)? Спасибо!

Ответ №1:

Альтернатива решению @mikksu, использующему pivot_table и reindex для большей гибкости:

 df = df.pivot_table(index='A', columns='N', values='value')
       .reindex(index=all_As, columns=all_Ns)
  

Вывод:

 N   0     1     2     3     4     5
A                                  
a NaN  11.0   NaN  15.0   NaN   NaN
b NaN   NaN   NaN   NaN  12.0  16.0
c NaN  17.0   NaN  13.0   NaN   NaN
d NaN   NaN  14.0   NaN   NaN   NaN
e NaN   NaN   NaN   NaN   NaN   NaN
  

Комментарии:

1. Ваше решение должно быть принятым ответом. Он может обрабатывать недостающие данные в каждой строке и столбце. Индексы могут быть созданы «на лету», например .reindex(index=list('abcdefg'), columns=range(7)) . И вы могли mean бы изменить sum значения, если это необходимо.

Ответ №2:

pandas.pivot делает то, что вы хотите. В вашем примере мы должны добавить данные для случаев e и 0 . Мы можем сделать это в одной новой строке.

 new_df = pd.pivot(
        df.append({'A':'e', 'N':0, 'value':np.nan},
            ignore_index=True),
        index='A',
        columns='N').apply(np.array, axis=1)
new_df
  

Вывод

 a    [nan, 11.0, nan, 15.0, nan, nan]
b    [nan, nan, nan, nan, 12.0, 16.0]
c    [nan, 17.0, nan, 13.0, nan, nan]
d     [nan, nan, 14.0, nan, nan, nan]
e      [nan, nan, nan, nan, nan, nan]
  

Более быстрый, немного другой подход. Исключение .apply(np.array, axis=1) будет намного быстрее для больших фреймов данных. И вы можете получить доступ к результирующему фрейму данных практически идентично.

 new_df = pd.pivot(
        df.append({'A':'e', 'N':0, 'value':np.nan},
            ignore_index=True),
        index='A',
        columns='N')
new_df
  

Вывод

 N     0     1     2     3     4     5
A                                    
a   NaN  11.0   NaN  15.0   NaN   NaN
b   NaN   NaN   NaN   NaN  12.0  16.0
c   NaN  17.0   NaN  13.0   NaN   NaN
d   NaN   NaN  14.0   NaN   NaN   NaN
e   NaN   NaN   NaN   NaN   NaN   NaN
  

Вы можете получить доступ к каждой строке в виде массива numpy с

 new_df.loc['a'].values
  

Вывод

 array([nan, 11., nan, 15., nan, nan])
  

Комментарии:

1. Спасибо! Это очень полезно. Не могли бы вы также описать, как обрабатывать дополнительную букву «e» на оси A?

2. Это то же самое. Вам нужна одна строка для каждого желаемого столбца или строки. Я обновил свой ответ, я пропустил второй пропущенный случай раньше.