Эффективно создавать разреженные сводные таблицы в pandas?
Я работаю, превращая список записей с двумя столбцами (A и B) в матричное представление. Я использую опорную функцию в pandas, но результат заканчивается довольно большим. Поддерживает ли поддержка pandas поворот в разреженном формате? Я знаю, что могу повернуть его, а затем превратить его в какое-то разреженное представление, но не так элегантно, как хотелось бы. Моя конечная цель состоит в том, чтобы использовать его как вход для предсказательной модели.
В качестве альтернативы существует ли какая-то разреженная возможность поворота за пределами pandas?
edit: вот пример нерезкого свода
import pandas as pd
frame=pd.DataFrame()
frame['person']=['me','you','him','you','him','me']
frame['thing']=['a','a','b','c','d','d']
frame['count']=[1,1,1,1,1,1]
frame
person thing count
0 me a 1
1 you a 1
2 him b 1
3 you c 1
4 him d 1
5 me d 1
frame.pivot('person','thing')
count
thing a b c d
person
him NaN 1 NaN 1
me 1 NaN NaN 1
you 1 NaN 1 NaN
Это создает матрицу, которая может содержать все возможные комбинации людей и вещей, но она не разрежена.
http://docs.scipy.org/doc/scipy/reference/sparse.html
Разреженные матрицы занимают меньше места, потому что они могут подразумевать такие вещи, как NaN или 0. Если у меня очень большой набор данных, эта функция поворота может генерировать матрицу, которая должна быть разреженной из-за большого количества NaN или 0s. Я надеялся, что смогу сэкономить много места/памяти, создав что-то, что было редко с самого начала, вместо создания плотной матрицы, а затем превратило ее в разреженный.
Ответы
Ответ 1
Ответ, опубликованный ранее @khammel, был полезен, но, к сожалению, больше не работает из-за изменений в пандах и Python. Следующее должно произвести тот же результат:
from scipy.sparse import csr_matrix
from pandas.api.types import CategoricalDtype
person_c = CategoricalDtype(sorted(frame.person.unique()), ordered=True)
thing_c = CategoricalDtype(sorted(frame.thing.unique()), ordered=True)
row = frame.person.astype(person_c).cat.codes
col = frame.thing.astype(thing_c).cat.codes
sparse_matrix = csr_matrix((frame["count"], (row, col)), \
shape=(person_c.categories.size, thing_c.categories.size))
>>> sparse_matrix
<3x4 sparse matrix of type '<class 'numpy.int64'>'
with 6 stored elements in Compressed Sparse Row format>
>>> sparse_matrix.todense()
matrix([[0, 1, 0, 1],
[1, 0, 0, 1],
[1, 0, 1, 0]], dtype=int64)
dfs = pd.SparseDataFrame(sparse_matrix, \
index=person_c.categories, \
columns=thing_c.categories, \
default_fill_value=0)
>>> dfs
a b c d
him 0 1 0 1
me 1 0 0 1
you 1 0 1 0
Основные изменения были:
-
.astype()
больше не принимает "категорический". Вы должны создать объект CategoryoricalDtype. -
sort()
больше не работает
Другие изменения были более поверхностными:
- используя размеры категорий вместо длины уникальных объектов Series, просто потому, что я не хотел создавать другой объект без необходимости
- ввод данных для
csr_matrix
(frame["count"]
) не обязательно должен быть объектом списка - pandas
SparseDataFrame
принимает объект scipy.sparse прямо сейчас
Ответ 2
Вот метод, который создает разреженную матрицу scipy на основе данных и индексов человека и вещи. person_u
и thing_u
- это списки, представляющие уникальные записи для ваших строк и столбцов стержня, который вы хотите создать. Примечание: это предполагает, что в столбце count уже есть значение, которое вы хотите в нем.
from scipy.sparse import csr_matrix
person_u = list(sort(frame.person.unique()))
thing_u = list(sort(frame.thing.unique()))
data = frame['count'].tolist()
row = frame.person.astype('category', categories=person_u).cat.codes
col = frame.thing.astype('category', categories=thing_u).cat.codes
sparse_matrix = csr_matrix((data, (row, col)), shape=(len(person_u), len(thing_u)))
>>> sparse_matrix
<3x4 sparse matrix of type '<type 'numpy.int64'>'
with 6 stored elements in Compressed Sparse Row format>
>>> sparse_matrix.todense()
matrix([[0, 1, 0, 1],
[1, 0, 0, 1],
[1, 0, 1, 0]])
Исходя из вашего первоначального вопроса, scipy разреженная матрица должна быть достаточной для ваших нужд, но если вы хотите иметь разреженный фреймворк, вы можете сделать следующее:
dfs=pd.SparseDataFrame([ pd.SparseSeries(sparse_matrix[i].toarray().ravel(), fill_value=0)
for i in np.arange(sparse_matrix.shape[0]) ], index=person_u, columns=thing_u, default_fill_value=0)
>>> dfs
a b c d
him 0 1 0 1
me 1 0 0 1
you 1 0 1 0
>>> type(dfs)
pandas.sparse.frame.SparseDataFrame
Ответ 3
У меня была аналогичная проблема, и я наткнулся на этот пост. Единственное различие заключалось в том, что у меня было два столбца в DataFrame
, которые определяют "размер строки" (i
) выходной матрицы. Я думал, что это может быть интересное обобщение, я использовал grouper
:
# function
import pandas as pd
from scipy.sparse import csr_matrix
def df_to_sm(data, vars_i, vars_j):
grpr_i = data.groupby(vars_i).grouper
idx_i = grpr_i.group_info[0]
grpr_j = data.groupby(vars_j).grouper
idx_j = grpr_j.group_info[0]
data_sm = csr_matrix((data['val'].values, (idx_i, idx_j)),
shape=(grpr_i.ngroups, grpr_j.ngroups))
return data_sm, grpr_i, grpr_j
# example
data = pd.DataFrame({'var_i_1' : ['a1', 'a1', 'a1', 'a2', 'a2', 'a3'],
'var_i_2' : ['b2', 'b1', 'b1', 'b1', 'b1', 'b4'],
'var_j_1' : ['c2', 'c3', 'c2', 'c1', 'c2', 'c3'],
'val' : [1, 2, 3, 4, 5, 6]})
data_sm, _, _ = df_to_sm(data, ['var_i_1', 'var_i_2'], ['var_j_1'])
data_sm.todense()