Эффективно создавать разреженные сводные таблицы в пандах?

Я работаю над превращением списка записей с двумя столбцами (A и B) в матричное представление. Я использовал функцию pivot в пандах, но в результате получаю довольно большой результат. Поддерживает ли панда поворот в разреженный формат? Я знаю, что могу повернуть его, а затем превратить в какое-то разреженное представление, но не так элегантно, как хотелось бы. Моя конечная цель - использовать его в качестве входных данных для прогнозирующей модели.

В качестве альтернативы, есть ли какая-то редкая возможность разворота за пределами панд?

редактировать: вот пример не разреженного центра

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 или 0. Я надеялся, что смогу сэкономить много места / памяти, сгенерировав что-то редкое сразу, а не создав плотную матрицу, а затем преобразовав ее в разреженную.

5 ответов

Решение

Вот метод, который создает скудную матрицу, основанную на данных и индексах человека и вещи. 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]])

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

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

Ответ, опубликованный ранее @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_u.categories, \
                         columns=thing_u.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"]) не должен быть объектом списка
  • панд SparseDataFrame принимает объект scipy.sparse прямо сейчас

У меня была похожая проблема, и я наткнулся на этот пост. Единственная разница была в том, что у меня было две колонки в 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()

Вот ответ, который обновляет подход в ответе @Alnilam, чтобы использовать современные библиотеки pandas, которые больше не содержат все функции в этом ответе.

      from scipy.sparse import csr_matrix
from pandas.api.types import CategoricalDtype

rcLabel, vLabel = ('person', 'thing'), 'count'
rcCat = [CategoricalDtype(sorted(frame[col].unique()), ordered=True) for col in rcLabel]
rc = [frame[column].astype(aType).cat.codes for column, aType in zip(rcLabel, rcCat)]
mat = csr_matrix((frame[vLabel], rc), shape=tuple(cat.categories.size for cat in rcCat))
dfPivot = ( pd.DataFrame.sparse.from_spmatrix(
    mat, index=rcCat[0].categories, columns=rcCat[1].categories) )

Я не знаю, когда это изменилось, но в Pandas v2.1.0, если ваш столбец значений являетсяpd.SparseDtype, тогда Pivot() и Pivot_table() будут генерировать разреженные столбцы.

Другие вопросы по тегам