#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. Это то же самое. Вам нужна одна строка для каждого желаемого столбца или строки. Я обновил свой ответ, я пропустил второй пропущенный случай раньше.